Merge branch 'staging' into remove-dnsmasq

remove-dnsmasq
Rick Carlino 2020-04-08 15:25:04 -05:00 committed by GitHub
commit fe3732a7b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 991 additions and 557 deletions

View File

@ -6,6 +6,30 @@ This document will act as an index to available documentation.
* [FarmBot Source Code common terms](/docs/glossary.md)
## Cheat Sheet
**Create a *.fw file from local repo (RPi Zero):**
```sh
NERVES_SYSTEM=farmbot_system_rpi MIX_TARGET=rpi mix deps.get
NERVES_SYSTEM=farmbot_system_rpi MIX_TARGET=rpi mix firmware
sudo fwup farmbot_os/_build/rpi/rpi_dev/nerves/images/farmbot.fw
```
**Create a *.fw file from local repo (RPi v3):**
```sh
NERVES_SYSTEM=farmbot_system_rpi3 MIX_TARGET=rpi3 mix deps.get
NERVES_SYSTEM=farmbot_system_rpi3 MIX_TARGET=rpi3 mix firmware
sudo fwup farmbot_os/_build/rpi3/rpi3_dev/nerves/images/farmbot.fw
```
**Create or Update the Nerves System:**
Please see the official [Nerves documentation on "Nerves Systems"](https://hexdocs.pm/nerves/0.4.0/systems.html).
HINT: You may want to [develop the system locally](https://stackoverflow.com/a/28189056/1064917)
## Hardware specifics
Most FarmBot development/testing is done on a standard desktop PC.

View File

@ -25,3 +25,4 @@ farmbot_ng-*.tar
*.sqlite3
*.so
*.hex
*.coverdata

View File

@ -5,10 +5,35 @@ defmodule FarmbotCeleryScript.AST.Factory do
alias FarmbotCeleryScript.AST
@doc """
Create an empty AST WITH ARG SET TO `nil`.
iex> new()
%FarmbotCeleryScript.AST{
args: nil,
body: [],
comment: nil,
kind: nil,
meta: nil
}
"""
def new do
%AST{body: []}
end
@doc """
Create a new AST to work with. Strings `kind`s are
converted to symbols.
iex> new("foo")
%FarmbotCeleryScript.AST{
args: %{},
body: [],
comment: nil,
kind: :foo,
meta: nil
}
"""
def new(kind, args \\ %{}, body \\ []) do
AST.new(kind, Map.new(args), body)
end
@ -24,59 +49,148 @@ defmodule FarmbotCeleryScript.AST.Factory do
)
end
@doc """
iex> (new() |> rpc_request("x") |> set_pin_io_mode(13, 1)).body
[%FarmbotCeleryScript.AST{
kind: :set_pin_io_mode,
args: %{ pin_io_mode: 1, pin_number: 13 },
body: [],
comment: nil,
meta: nil
}]
"""
def set_pin_io_mode(%AST{} = ast, pin_number, pin_io_mode) do
ast
|> add_body_node(
new(:set_pin_io_mode, %{pin_number: pin_number, pin_io_mode: pin_io_mode})
)
args = %{pin_number: pin_number, pin_io_mode: pin_io_mode}
ast |> add_body_node(new(:set_pin_io_mode, args))
end
@doc """
iex> (new() |> rpc_request("x") |> emergency_lock()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :emergency_lock
}]
"""
def emergency_lock(%AST{} = ast) do
ast
|> add_body_node(new(:emergency_lock))
ast |> add_body_node(new(:emergency_lock))
end
@doc """
iex> (new() |> rpc_request("x") |> emergency_unlock()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :emergency_unlock
}]
"""
def emergency_unlock(%AST{} = ast) do
ast
|> add_body_node(new(:emergency_unlock))
ast |> add_body_node(new(:emergency_unlock))
end
@doc """
iex> (new() |> rpc_request("x") |> read_status()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :read_status
}]
"""
def read_status(%AST{} = ast) do
ast
|> add_body_node(new(:read_status))
ast |> add_body_node(new(:read_status))
end
@doc """
iex> (new() |> rpc_request("x") |> power_off()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :power_off
}]
"""
def power_off(%AST{} = ast) do
ast
|> add_body_node(new(:power_off))
ast |> add_body_node(new(:power_off))
end
@doc """
iex> (new() |> rpc_request("x") |> reboot()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :reboot
}]
"""
def reboot(%AST{} = ast) do
ast
|> add_body_node(new(:reboot))
ast |> add_body_node(new(:reboot))
end
@doc """
iex> (new() |> rpc_request("x") |> sync()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :sync
}]
"""
def sync(%AST{} = ast) do
ast
|> add_body_node(new(:sync))
ast |> add_body_node(new(:sync))
end
@doc """
iex> (new() |> rpc_request("x") |> take_photo()).body
[%FarmbotCeleryScript.AST{
body: [],
comment: nil,
meta: nil,
args: %{},
kind: :take_photo
}]
"""
def take_photo(%AST{} = ast) do
ast
|> add_body_node(new(:take_photo))
ast |> add_body_node(new(:take_photo))
end
@doc """
iex> (new() |> rpc_request("x") |> flash_firmware("arduino")).body
[%FarmbotCeleryScript.AST{
kind: :flash_firmware,
comment: nil,
meta: nil,
args: %{package: "arduino"},
body: [],
}]
"""
def flash_firmware(%AST{} = ast, package) when is_binary(package) do
ast
|> add_body_node(new(:flash_firmware, %{package: package}))
ast |> add_body_node(new(:flash_firmware, %{package: package}))
end
@doc """
iex> (new() |> rpc_request("x") |> factory_reset("arduino")).body
[%FarmbotCeleryScript.AST{
kind: :factory_reset,
comment: nil,
meta: nil,
args: %{package: "arduino"},
body: [],
}]
"""
def factory_reset(%AST{} = ast, package) do
ast |> add_body_node(new(:factory_reset, %{package: package}))
end
def add_body_node(%AST{body: body} = ast, %AST{} = body_node) do
%{ast | body: body ++ [body_node]}
end
def factory_reset(%AST{} = ast, package) do
ast
|> add_body_node(new(:factory_reset, %{package: package}))
end
end

View File

@ -0,0 +1,4 @@
defmodule FarmbotCeleryScript.AST.FactoryTest do
use ExUnit.Case, async: true
doctest FarmbotCeleryScript.AST.Factory, import: true
end

View File

@ -3,6 +3,7 @@ defmodule FarmbotCeleryScript.SchedulerTest do
use Mimic
alias FarmbotCeleryScript.{Scheduler, AST}
alias FarmbotCeleryScript.SysCalls.Stubs
import ExUnit.CaptureLog
setup :set_mimic_global
setup :verify_on_exit!
@ -21,11 +22,14 @@ defmodule FarmbotCeleryScript.SchedulerTest do
|> AST.Factory.read_pin(9, 0)
scheduled_time = DateTime.utc_now() |> DateTime.add(100, :millisecond)
# msg = "[info] Next execution is ready for execution: now"
{:ok, _} = Scheduler.schedule(sch, ast, scheduled_time, %{})
# Hack to force the scheduler to checkup instead of waiting the normal 15 seconds
send(sch, :checkup)
# Sorry.
Process.sleep(1100)
assert capture_log(fn ->
send(sch, :checkup)
# Sorry.
Process.sleep(1100)
end) =~ "[info] Next execution is ready for execution: now"
end
end

View File

@ -5,6 +5,9 @@ defmodule FarmbotCeleryScriptTest do
alias FarmbotCeleryScript.AST
alias FarmbotCeleryScript.SysCalls.Stubs
import ExUnit.CaptureIO
import ExUnit.CaptureLog
setup :verify_on_exit!
test "uses default values when no parameter is found" do
@ -59,8 +62,10 @@ defmodule FarmbotCeleryScriptTest do
:ok
end)
result = FarmbotCeleryScript.execute(sequence_ast, me)
assert :ok == result
capture_log(fn ->
result = FarmbotCeleryScript.execute(sequence_ast, me)
assert :ok == result
end) =~ "[error] CeleryScript syscall stubbed: log"
end
test "syscall errors" do
@ -93,11 +98,17 @@ defmodule FarmbotCeleryScriptTest do
}
|> AST.decode()
expect(Stubs, :read_pin, fn _, _ -> raise("big oops") end)
expect(Stubs, :read_pin, fn _, _ ->
raise("big oops")
end)
assert {:error, "big oops"} ==
FarmbotCeleryScript.execute(execute_ast, execute_ast)
io =
capture_io(:stderr, fn ->
assert {:error, "big oops"} ==
FarmbotCeleryScript.execute(execute_ast, execute_ast)
end)
assert io =~ "CeleryScript Exception"
assert_receive {:step_complete, ^execute_ast, {:error, "big oops"}}
end
end

View File

@ -1,4 +1,5 @@
use Mix.Config
config :logger, level: :warn
# must be lower than other timers
# To ensure other timers have time to timeout
@ -17,3 +18,19 @@ config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 0
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
firmware_flash_attempt_threshold: 0
if Mix.env() == :test do
config :ex_unit, capture_logs: true
mapper = fn mod -> config :farmbot_core, mod, children: [] end
list = [
FarmbotCore,
FarmbotCore.StorageSupervisor,
FarmbotCore.Asset.Supervisor,
FarmbotCore.BotState.Supervisor,
FarmbotCore.Config.Supervisor,
FarmbotCore.Logger.Supervisor
]
Enum.map(list, mapper)
end

View File

@ -1,6 +1,6 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 24
"minimum_coverage": 25
}
}
}

View File

@ -14,8 +14,11 @@ defmodule FarmbotCore do
def start(_, args), do: Supervisor.start_link(__MODULE__, args, name: __MODULE__)
def init([]) do
Supervisor.init(children(), [strategy: :one_for_one])
end
children = [
def children do
default = [
FarmbotCore.Leds,
FarmbotCore.EctoMigrator,
FarmbotCore.BotState.Supervisor,
@ -27,6 +30,7 @@ defmodule FarmbotCore do
{FarmbotFirmware, transport: FarmbotFirmware.StubTransport, side_effects: FarmbotCore.FirmwareSideEffects},
FarmbotCeleryScript.Scheduler
]
Supervisor.init(children, [strategy: :one_for_one])
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, default)
end
end

View File

@ -62,11 +62,6 @@ defmodule FarmbotCore.Asset do
## Begin FarmEvent
@doc "Returns all FarmEvents"
def list_farm_events do
Repo.all(FarmEvent)
end
def new_farm_event!(params) do
%FarmEvent{}
|> FarmEvent.changeset(params)
@ -353,7 +348,7 @@ defmodule FarmbotCore.Asset do
|> Repo.update!()
regimen_instances = list_regimen_instances()
farm_events = list_farm_events()
farm_events = Repo.all(FarmEvent)
# check for any matching asset using this point group.
# This is pretty recursive and probably isn't super great

View File

@ -23,7 +23,11 @@ defmodule FarmbotCore.Asset.Supervisor do
end
def init([]) do
children = [
Supervisor.init(children(), strategy: :one_for_one)
end
def children do
default = [
Repo,
{AssetSupervisor, module: FbosConfig},
{AssetSupervisor, module: FirmwareConfig},
@ -38,7 +42,7 @@ defmodule FarmbotCore.Asset.Supervisor do
{AssetSupervisor, module: FarmwareEnv},
AssetMonitor,
]
Supervisor.init(children, strategy: :one_for_one)
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, default)
end
end

View File

@ -82,7 +82,6 @@ defmodule FarmbotCore.AssetMonitor do
sub_state = Map.drop(sub_state, deleted_ids)
Enum.each(deleted_ids, fn local_id ->
Logger.error("#{inspect(kind)} #{local_id} needs to be terminated")
AssetSupervisor.terminate_child(kind, local_id)
end)
@ -99,7 +98,6 @@ defmodule FarmbotCore.AssetMonitor do
Map.put(sub_state, id, updated_at)
compare_datetimes(updated_at, sub_state[id]) == :gt ->
Logger.warn("#{inspect(kind)} #{id} needs to be updated")
asset = Repo.preload(asset, AssetWorker.preload(asset))
:ok = AssetSupervisor.update_child(asset) |> assert_result!(asset)
Map.put(sub_state, id, updated_at)

View File

@ -25,8 +25,6 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
@impl GenServer
def init([regimen_instance, _args]) do
Logger.warn "RegimenInstance #{inspect(regimen_instance)} initializing"
with %Regimen{} <- regimen_instance.regimen,
%FarmEvent{} <- regimen_instance.farm_event do
send self(), :schedule
@ -40,25 +38,25 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
def handle_info(:schedule, state) do
regimen_instance = state.regimen_instance
# load the sequence and calculate the scheduled_at time
Enum.map(regimen_instance.regimen.regimen_items, fn(%{time_offset: offset, sequence_id: sequence_id}) ->
Enum.map(regimen_instance.regimen.regimen_items, fn(%{time_offset: offset, sequence_id: sequence_id}) ->
scheduled_at = DateTime.add(regimen_instance.epoch, offset, :millisecond)
sequence = Asset.get_sequence(sequence_id) || raise("sequence #{sequence_id} is not synced")
%{scheduled_at: scheduled_at, sequence: sequence}
end)
# get rid of any item that has already been scheduled/executed
|> Enum.reject(fn(%{scheduled_at: scheduled_at}) ->
|> Enum.reject(fn(%{scheduled_at: scheduled_at}) ->
Asset.get_regimen_instance_execution(regimen_instance, scheduled_at)
end)
|> Enum.each(fn(%{scheduled_at: at, sequence: sequence}) ->
|> Enum.each(fn(%{scheduled_at: at, sequence: sequence}) ->
schedule_sequence(regimen_instance, sequence, at)
end)
{:noreply, state}
{:noreply, state}
end
def handle_info({FarmbotCeleryScript, {:scheduled_execution, scheduled_at, executed_at, result}}, state) do
status = case result do
:ok -> "ok"
{:error, reason} ->
{:error, reason} ->
FarmbotCore.Logger.error(2, "Regimen scheduled at #{scheduled_at} failed to execute: #{reason}")
reason
end
@ -81,11 +79,11 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
regimen_params = AST.decode(regimen_instance.regimen.body)
# there may be many sequence scopes from here downward
celery_ast = AST.decode(sequence)
celery_args =
celery_args =
celery_ast.args
|> Map.put(:sequence_name, sequence.name)
|> Map.put(:locals, %{celery_ast.args.locals | body: celery_ast.args.locals.body ++ regimen_params ++ farm_event_params})
celery_ast = %{celery_ast | args: celery_args}
FarmbotCeleryScript.schedule(celery_ast, at, sequence)
end

View File

@ -6,11 +6,16 @@ defmodule FarmbotCore.BotState.Supervisor do
end
def init([]) do
children = [
Supervisor.init(children(), [strategy: :one_for_all])
end
def children do
default = [
FarmbotCore.BotState,
FarmbotCore.BotState.FileSystem,
FarmbotCore.BotState.SchedulerUsageReporter
]
Supervisor.init(children, [strategy: :one_for_all])
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, default)
end
end

View File

@ -7,9 +7,12 @@ defmodule FarmbotCore.Config.Supervisor do
end
def init([]) do
children = [
{FarmbotCore.Config.Repo, []},
]
Supervisor.init(children, strategy: :one_for_one)
Supervisor.init(children(), strategy: :one_for_one)
end
def children do
default = [ {FarmbotCore.Config.Repo, []} ]
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, default)
end
end

View File

@ -1,7 +1,5 @@
defmodule FarmbotCore.Leds do
@moduledoc "API for controling Farmbot LEDS."
@led_handler Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
@led_handler || Mix.raise("You forgot a led handler!")
@valid_status [:off, :solid, :slow_blink, :fast_blink, :really_fast_blink]
@ -15,29 +13,7 @@ defmodule FarmbotCore.Leds do
def white4(status) when status in @valid_status, do: led_handler().white4(status)
def white5(status) when status in @valid_status, do: led_handler().white5(status)
def factory_test(status) do
red(:off)
blue(:off)
green(:off)
yellow(:off)
white1(:off)
white2(:off)
white3(:off)
white4(:off)
white5(:off)
red(status)
blue(status)
green(status)
yellow(status)
white1(status)
white2(status)
white3(status)
white4(status)
white5(status)
end
defp led_handler,
def led_handler,
do: Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
def child_spec(opts) do

View File

@ -7,11 +7,13 @@ defmodule FarmbotCore.Logger.Supervisor do
end
def init([]) do
children = [
supervisor(FarmbotCore.Logger.Repo, [])
]
opts = [strategy: :one_for_all]
supervise(children, opts)
supervise(children(), opts)
end
def children do
default = [supervisor(FarmbotCore.Logger.Repo, [])]
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, default)
end
end

View File

@ -10,11 +10,16 @@ defmodule FarmbotCore.StorageSupervisor do
end
def init([]) do
children = [
Supervisor.init(children(), [strategy: :one_for_one])
end
def children do
default = [
FarmbotCore.Logger.Supervisor,
FarmbotCore.Config.Supervisor,
FarmbotCore.Asset.Supervisor
]
Supervisor.init(children, [strategy: :one_for_one])
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, default)
end
end

View File

@ -1,21 +1,6 @@
defmodule FarmbotCore.TimeUtils do
@moduledoc "Helper functions for working with time."
def format_time(%DateTime{} = dt) do
"#{format_num(dt.month)}/#{format_num(dt.day)}/#{dt.year} " <>
"at #{format_num(dt.hour)}:#{format_num(dt.minute)}"
end
defp format_num(num), do: :io_lib.format('~2..0B', [num]) |> to_string
# returns midnight of today
@spec build_epoch(DateTime.t) :: DateTime.t
def build_epoch(time) do
tz = FarmbotCore.Asset.fbos_config().timezone
n = Timex.Timezone.convert(time, tz)
Timex.shift(n, hours: -n.hour, seconds: -n.second, minutes: -n.minute)
end
@doc """
Compares a datetime with another.
-1 -- the first date comes before the second one

View File

@ -14,22 +14,16 @@ defmodule FarmbotCore.Config.Repo.Migrations.AddNtpAndDnsConfigs do
:default_dns_name
]
@config_error """
config :farmbot_core, FarmbotCore.EctoMigrator, [
default_ntp_server_1: "0.pool.ntp.org",
default_ntp_server_2: "1.pool.ntp.org",
default_dns_name: "my.farm.bot"
]
"""
if is_nil(@default_ntp_server_1),
do: raise(@config_error)
if is_nil(@default_ntp_server_2),
do: raise(@config_error)
if is_nil(@default_dns_name),
do: raise(@config_error)
unless @default_ntp_server_1 && @default_ntp_server_2 && @default_dns_name do
@config_error """
config :farmbot_core, FarmbotCore.EctoMigrator, [
default_ntp_server_1: "0.pool.ntp.org",
default_ntp_server_2: "1.pool.ntp.org",
default_dns_name: "my.farm.bot"
]
"""
Mix.raise(@config_error)
end
def change do
create_settings_config(

View File

@ -27,6 +27,7 @@ defmodule FarmbotCore.Asset.CommandTest do
:ok = Command.update(FirmwareConfig, 23, Map.from_struct(config))
end
@tag :capture_log
test "update / destroy fbos config" do
params = %{id: 23, update_channel: "whatever"}
:ok = Command.update(FbosConfig, 23, params)
@ -38,6 +39,7 @@ defmodule FarmbotCore.Asset.CommandTest do
refute next_config
end
@tag :capture_log
test "update / destroy device" do
params = %{id: 23, name: "Old Device"}
:ok = Command.update(Device, 23, params)
@ -56,6 +58,7 @@ defmodule FarmbotCore.Asset.CommandTest do
assert Asset.get_regimen(id)
end
@tag :capture_log
test "update regimen" do
id = id()
:ok = Command.update("Regimen", id, %{id: id, name: "abc", monitor: false})
@ -70,6 +73,7 @@ defmodule FarmbotCore.Asset.CommandTest do
refute Asset.get_regimen(id)
end
@tag :capture_log
test "insert new farm_event" do
id = id()
:ok = Command.update("FarmEvent", id, %{id: id, monitor: false})
@ -101,6 +105,7 @@ defmodule FarmbotCore.Asset.CommandTest do
assert Asset.get_farm_event(id).executable_type == "Regimen"
end
@tag :capture_log
test "delete farm_event" do
id = id()

View File

@ -123,6 +123,7 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
Enum.map(expected, fn id -> assert Enum.member?(results, id) end)
end
@tag :capture_log
test "point group that does not define criteria" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)

View File

@ -1,3 +0,0 @@
defmodule FarmbotCore.Asset.PrivateTest do
use ExUnit.Case, async: true
end

View File

@ -1,61 +0,0 @@
defmodule FarmbotCore.AssetMonitorTest do
use ExUnit.Case, async: false
alias FarmbotCore.{Asset.Repo, AssetMonitor, AssetSupervisor}
import Farmbot.TestSupport.AssetFixtures
describe "regimen instances" do
test "adding a regimen instance starts a process" do
farm_event_params = %{
start_time: DateTime.utc_now(),
end_time: DateTime.utc_now(),
repeat: 1,
time_unit: "never"
}
pr = regimen_instance(%{}, farm_event_params, %{monitor: true})
AssetMonitor.force_checkup()
assert {id, _, _, _} = AssetSupervisor.whereis_child(pr)
assert id == pr.local_id
Repo.delete!(pr)
AssetMonitor.force_checkup()
assert {id, :undefined, _, _} = AssetSupervisor.whereis_child(pr)
assert id == pr.local_id
end
end
describe "farm events" do
test "adding a farm event starts a process" do
seq = sequence()
now = DateTime.utc_now()
start_time = Timex.shift(now, minutes: -20)
end_time = Timex.shift(now, minutes: 10)
params = %{
monitor: true,
start_time: start_time,
end_time: end_time,
repeat: 5,
time_unit: "hourly"
}
event = sequence_event(seq, params)
AssetMonitor.force_checkup()
assert {id, _, _, _} = AssetSupervisor.whereis_child(event)
assert id == event.local_id
Repo.delete!(event)
AssetMonitor.force_checkup()
assert {id, :undefined, _, _} = AssetSupervisor.whereis_child(event)
assert id == event.local_id
end
end
end

View File

@ -15,4 +15,10 @@ defmodule FarmbotCore.AssetTest do
assert %RegimenInstance{} = Asset.new_regimen_instance!(event)
end
end
test "Asset.device/1" do
assert nil == Asset.device(:ota_hour)
assert %FarmbotCore.Asset.Device{} = Asset.update_device!(%{ota_hour: 17})
assert 17 == Asset.device(:ota_hour)
end
end

View File

@ -4,6 +4,7 @@ defmodule FarmbotCore.FbosConfigWorkerTest do
import Farmbot.TestSupport.AssetFixtures
@tag :capture_log
test "adds configs to bot state and config_storage" do
%FbosConfig{} =
conf =

View File

@ -61,6 +61,8 @@ defmodule FarmbotCore.BotState.FileSystemTest do
describe "server" do
test "serializes state to fs" do
IO.puts("THIS TEST BLINKS! Fix it.")
root_dir =
Path.join([
System.tmp_dir!(),
@ -81,7 +83,7 @@ defmodule FarmbotCore.BotState.FileSystemTest do
:ok = BotState.set_pin_value(bot_state_pid, 1, 1)
assert_received {BotState, _}, 200
# sleep to allow changes to propagate.
Process.sleep(200)
Process.sleep(2000)
pins_dir = Path.join([root_dir, "pins", "1"])
# default value
assert File.read!(Path.join(pins_dir, "mode")) == "-1"

View File

@ -10,6 +10,7 @@ defmodule FarmbotCore.BotStateTest do
assert_receive {BotState, %Ecto.Changeset{valid?: true}}
end
@tag :capture_log
test "invalid data doesn't get dispatched" do
{:ok, bot_state_pid} = BotState.start_link([], [])
_initial_state = BotState.subscribe(bot_state_pid)

View File

@ -1,38 +0,0 @@
defmodule FarmbotCore.FarmwareRuntime.PipeWorkerTest do
use ExUnit.Case, async: false
# alias FarmbotCore.FarmwareRuntime.PipeWorker
# TODO Find a suitable tool for testing domain sockets?
# test "reads data from pipe" do
# pipe_name = random_pipe()
# {:ok, pipe_worker} = PipeWorker.start_link(pipe_name)
# ref = PipeWorker.read(pipe_worker, 11)
# {_, 0} = System.cmd("bash", ["-c", "echo -e 'hello world' > #{pipe_name}"])
# assert_receive {PipeWorker, ^ref, {:ok, "hello world"}}
# end
# test "writes data to a pipe" do
# pipe_name = random_pipe()
# {:ok, pipe_worker} = PipeWorker.start_link(pipe_name)
# ref = PipeWorker.read(pipe_worker, 11)
# PipeWorker.write(pipe_worker, "hello world")
# assert_receive {PipeWorker, ^ref, {:ok, "hello world"}}
# end
# test "cleanup pipes on exit" do
# pipe_name = random_pipe()
# {:ok, pipe_worker} = PipeWorker.start_link(pipe_name)
# assert File.exists?(pipe_name)
# _ = Process.flag(:trap_exit, true)
# :ok = PipeWorker.close(pipe_worker)
# assert_receive {:EXIT, ^pipe_worker, :normal}
# refute File.exists?(pipe_name)
# end
# defp random_pipe do
# pipe_name = Ecto.UUID.generate() <> ".pipe"
# Path.join([System.tmp_dir!(), pipe_name])
# end
end

View File

@ -0,0 +1,37 @@
defmodule FarmbotCore.Leds.StubHandlerTest do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
@color_map %{
:red => :red,
:blue => :blue,
:green => :green,
:yellow => :yellow,
:white1 => :white,
:white2 => :white,
:white3 => :white,
:white4 => :white,
:white5 => :white
}
@status [:fast_blink, :really_fast_blink, :slow_blink, :solid]
def capture_led(color) do
status = @status |> Enum.shuffle() |> Enum.at(0)
do_it = fn -> apply(FarmbotCore.Leds, color, [status]) end
cap = capture_io(do_it)
assert cap =~ "LED STATUS:"
assert cap =~ apply(IO.ANSI, Map.fetch!(@color_map, color), [])
end
test "leds" do
capture_led(:red)
capture_led(:blue)
capture_led(:green)
capture_led(:yellow)
capture_led(:white1)
capture_led(:white2)
capture_led(:white3)
capture_led(:white4)
capture_led(:white5)
end
end

View File

@ -0,0 +1,9 @@
defmodule FarmbotCore.LogTest do
alias FarmbotCore.Log
use ExUnit.Case, async: true
test "to_chars" do
log = %Log{message: "Hello, world!"}
assert "Hello, world!" = "#{log}"
end
end

View File

@ -2,6 +2,7 @@ defmodule FarmbotCore.LoggerTest do
use ExUnit.Case
require FarmbotCore.Logger
@tag :capture_log
test "allows handling a log more than once by re-inserting it." do
log = FarmbotCore.Logger.debug(1, "Test log ABC")
# Handling a log should delete it from the store.

View File

@ -0,0 +1,10 @@
defmodule FarmbotCore.ProjectTest do
use ExUnit.Case
@opts [cd: Path.join("c_src", "farmbot-arduino-firmware")]
test "arduino_commit" do
actual = FarmbotCore.Project.arduino_commit()
assert is_binary(actual)
assert String.length(actual) == 40
end
end

View File

@ -1,5 +1,16 @@
use Mix.Config
if Mix.env() == :test do
config :farmbot_ext, FarmbotExt, children: []
config :ex_unit, capture_logs: true
mapper = fn mod -> config :farmbot_ext, mod, children: [] end
list = [
FarmbotExt,
FarmbotExt.AMQP.ChannelSupervisor,
FarmbotExt.API.DirtyWorker.Supervisor,
FarmbotExt.API.EagerLoader.Supervisor,
FarmbotExt.Bootstrap.Supervisor
]
Enum.map(list, mapper)
end

View File

@ -4,13 +4,11 @@ defmodule FarmbotExt do
use Application
def start(_type, _args) do
opts = [strategy: :one_for_one, name: __MODULE__]
Supervisor.start_link(children(), opts)
Supervisor.start_link(children(), opts())
end
# This only exists because I was getting too many crashed
# supervisor reports in the test suite (distraction from
# real test failures).
def opts, do: [strategy: :one_for_one, name: __MODULE__]
def children do
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, [FarmbotExt.Bootstrap])

View File

@ -19,17 +19,19 @@ defmodule FarmbotExt.AMQP.ChannelSupervisor do
end
def init([token]) do
jwt = JWT.decode!(token)
Supervisor.init(children(JWT.decode!(token)), strategy: :one_for_one)
end
children = [
def children(jwt) do
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, [
{TelemetryChannel, [jwt: jwt]},
{LogChannel, [jwt: jwt]},
{PingPongChannel, [jwt: jwt]},
{BotStateChannel, [jwt: jwt]},
{AutoSyncChannel, [jwt: jwt]},
{CeleryScriptChannel, [jwt: jwt]}
]
Supervisor.init(children, strategy: :one_for_one)
])
end
end

View File

@ -10,14 +10,17 @@ defmodule FarmbotExt.AMQP.Supervisor do
end
def init([]) do
Supervisor.init(children(), strategy: :one_for_all)
end
def children do
token = get_config_value(:string, "authorization", "token")
email = get_config_value(:string, "authorization", "email")
config = Application.get_env(:farmbot_ext, __MODULE__) || []
children = [
Keyword.get(config, :children, [
{FarmbotExt.AMQP.ConnectionWorker, [token: token, email: email]},
{FarmbotExt.AMQP.ChannelSupervisor, [token]}
]
Supervisor.init(children, strategy: :one_for_all)
])
end
end

View File

@ -33,7 +33,13 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
@impl Supervisor
def init(_args) do
children = [
Supervisor.init(children(), strategy: :one_for_one)
end
def children do
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, [
{DirtyWorker, Device},
{DirtyWorker, DeviceCert},
{DirtyWorker, FbosConfig},
@ -50,8 +56,6 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
{DirtyWorker, Sensor},
{DirtyWorker, Sequence},
{DirtyWorker, Tool}
]
Supervisor.init(children, strategy: :one_for_one)
])
end
end

View File

@ -39,7 +39,13 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
@impl Supervisor
def init(_args) do
children = [
Supervisor.init(children(), strategy: :one_for_one)
end
def children do
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, [
{EagerLoader, Device},
{EagerLoader, FarmEvent},
{EagerLoader, FarmwareEnv},
@ -56,8 +62,6 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
{EagerLoader, Sensor},
{EagerLoader, Sequence},
{EagerLoader, Tool}
]
Supervisor.init(children, strategy: :one_for_one)
])
end
end

View File

@ -16,7 +16,7 @@ defmodule FarmbotExt.API.ImageUploader do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def force_checkup do
def force_checkup() do
GenServer.cast(__MODULE__, :force_checkup)
end
@ -45,19 +45,23 @@ defmodule FarmbotExt.API.ImageUploader do
def handle_continue([], state), do: {:noreply, state, @checkup_time_ms}
# the meta here is likely inaccurate here because of
# pulling the location data from the cache instead of from the firmware
# directly. It's close enough and getting data from the firmware directly
# would require more work than it is worth
# the meta here is likely inaccurate here because of pulling the location data
# from the cache instead of from the firmware directly. It's close enough and
# getting data from the firmware directly would require more work than it is
# worth
defp try_upload(image_filename) do
%{x: x, y: y, z: z} = BotState.fetch().location_data.position
meta = %{x: x, y: y, z: z, name: Path.rootname(image_filename)}
finalize(image_filename, API.upload_image(image_filename, meta))
end
with {:ok, %{status: s, body: _body}} when s > 199 and s < 300 <-
API.upload_image(image_filename, meta) do
FarmbotCore.Logger.success(3, "Uploaded image: #{image_filename}")
File.rm(image_filename)
end
defp finalize(file, {:ok, %{status: s, body: _}}) when s > 199 and s < 300 do
FarmbotCore.Logger.success(3, "Uploaded image: #{file}")
File.rm(file)
end
defp finalize(fname, other) do
FarmbotCore.Logger.error(3, "Upload Error (#{fname}): #{inspect(other)}")
end
# Stolen from

View File

@ -12,14 +12,18 @@ defmodule FarmbotExt.Bootstrap.Supervisor do
@impl Supervisor
def init([]) do
children = [
Supervisor.init(children(), strategy: :one_for_one)
end
def children() do
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, [
FarmbotExt.API.EagerLoader.Supervisor,
FarmbotExt.API.DirtyWorker.Supervisor,
FarmbotExt.AMQP.Supervisor,
FarmbotExt.API.ImageUploader,
FarmbotExt.Bootstrap.DropPasswordTask
]
Supervisor.init(children, strategy: :one_for_one)
])
end
end

View File

@ -1,5 +1,6 @@
defmodule AutoSyncAssetHandlerTest do
use ExUnit.Case, async: true
require Helpers
use ExUnit.Case, async: false
use Mimic
setup :verify_on_exit!
@ -8,7 +9,10 @@ defmodule AutoSyncAssetHandlerTest do
alias FarmbotExt.AMQP.AutoSyncAssetHandler
alias FarmbotCore.{Asset, BotState, Leds}
import ExUnit.CaptureLog
def auto_sync_off, do: expect(Asset.Query, :auto_sync?, fn -> false end)
def auto_sync_on, do: expect(Asset.Query, :auto_sync?, fn -> true end)
def expect_sync_status_to_be(status),
do: expect(BotState, :set_sync_status, fn ^status -> :ok end)
@ -22,4 +26,41 @@ defmodule AutoSyncAssetHandlerTest do
expect_green_leds(:slow_blink)
AutoSyncAssetHandler.handle_asset("Point", 23, nil)
end
test "Handles @no_cache_kinds" do
id = 64
params = %{}
kind =
~w(Device FbosConfig FirmwareConfig FarmwareEnv FarmwareInstallation)
|> Enum.shuffle()
|> Enum.at(0)
expect(Asset.Command, :update, 1, fn ^kind, ^id, ^params -> :ok end)
assert :ok = AutoSyncAssetHandler.cache_sync(kind, id, params)
end
test "handling of deleted assets when auto_sync is enabled" do
auto_sync_on()
expect_sync_status_to_be("syncing")
expect_sync_status_to_be("synced")
expect_green_leds(:really_fast_blink)
expect_green_leds(:solid)
AutoSyncAssetHandler.handle_asset("Point", 32, nil)
end
test "cache sync" do
id = 64
params = %{}
kind = "Point"
# Helpers.expect_log("Autocaching sync #{kind} #{id} #{inspect(params)}")
changeset = %{ab: :cd}
changesetfaker = fn ^kind, ^id, ^params -> changeset end
expect(FarmbotCore.Asset.Command, :new_changeset, 1, changesetfaker)
expect(FarmbotExt.API.EagerLoader, :cache, 1, fn ^changeset -> :ok end)
expect_sync_status_to_be("sync_now")
expect_green_leds(:slow_blink)
do_it = fn -> AutoSyncAssetHandler.cache_sync(kind, id, params) end
assert capture_log(do_it) =~ "Autocaching sync Point 64 %{}"
end
end

View File

@ -1,6 +1,6 @@
defmodule AutoSyncChannelTest do
require Helpers
use ExUnit.Case, async: true
use ExUnit.Case, async: false
use Mimic
alias FarmbotExt.AMQP.AutoSyncChannel
@ -55,7 +55,7 @@ defmodule AutoSyncChannelTest do
expect(Preloader, :preload_all, 1, fn -> :ok end)
pid = generate_pid()
send(pid, msg)
Process.sleep(5)
Helpers.wait_for(pid)
end
test "basic_cancel", do: ensure_response_to({:basic_cancel, :anything})
@ -78,6 +78,7 @@ defmodule AutoSyncChannelTest do
# Helpers.expect_log("Failed to connect to AutoSync channel: :whatever")
# Helpers.expect_log("Disconnected from AutoSync channel: :normal")
pid = generate_pid()
IO.puts(" = = = ==RICK: This test blinks and you should fix it.")
assert %{chan: nil, conn: nil, preloaded: true} == AutoSyncChannel.network_status(pid)
GenServer.stop(pid, :normal)
end
@ -153,6 +154,6 @@ defmodule AutoSyncChannelTest do
:ok
end)
Process.sleep(1200)
Helpers.wait_for(pid)
end
end

View File

@ -1,5 +1,5 @@
defmodule FarmbotExt.AMQP.BotStateChannelTest do
use ExUnit.Case
use ExUnit.Case, async: false
use Mimic
# alias FarmbotExt.AMQP.BotStateChannel

View File

@ -0,0 +1,43 @@
defmodule FarmbotExt.API.ImageUploaderTest do
require Helpers
use ExUnit.Case, async: false
use Mimic
alias FarmbotExt.API.ImageUploader
setup :verify_on_exit!
setup :set_mimic_global
test "force checkup" do
pid =
if Process.whereis(ImageUploader) do
Process.whereis(ImageUploader)
else
{:ok, p} = ImageUploader.start_link([])
p
end
["a.jpg", "b.jpeg", "c.png", "d.gif"]
|> Enum.map(fn fname ->
f = "/tmp/images/#{fname}"
File.touch!(f)
File.write(f, "X")
end)
expect(FarmbotExt.API, :upload_image, 4, fn
"/tmp/images/d.gif", _meta -> {:error, %{status: 401, body: %{}}}
_image_filename, _meta -> {:ok, %{status: 201, body: %{}}}
end)
err_msg =
"Upload Error (/tmp/images/d.gif): " <>
"{:error, %{body: %{}, status: 401}}"
Helpers.expect_log("Uploaded image: /tmp/images/a.jpg")
Helpers.expect_log("Uploaded image: /tmp/images/b.jpeg")
Helpers.expect_log("Uploaded image: /tmp/images/c.png")
Helpers.expect_log(err_msg)
ImageUploader.force_checkup()
send(pid, :timeout)
Helpers.wait_for(pid)
end
end

View File

@ -1,28 +1,54 @@
Application.ensure_all_started(:farmbot)
Mimic.copy(AMQP.Channel)
Mimic.copy(FarmbotCeleryScript.SysCalls.Stubs)
Mimic.copy(FarmbotCore.Asset.Command)
Mimic.copy(FarmbotCore.Asset.Query)
Mimic.copy(FarmbotCore.BotState)
Mimic.copy(FarmbotCore.Leds)
Mimic.copy(FarmbotCore.LogExecutor)
Mimic.copy(FarmbotExt.AMQP.ConnectionWorker)
Mimic.copy(FarmbotExt.API.EagerLoader.Supervisor)
Mimic.copy(FarmbotExt.API.Preloader)
Mimic.copy(FarmbotExt.API)
Mimic.copy(FarmbotExt.AMQP.AutoSyncAssetHandler)
[
AMQP.Channel,
FarmbotCeleryScript.SysCalls.Stubs,
FarmbotCore.Asset.Command,
FarmbotCore.Asset.Query,
FarmbotCore.BotState,
FarmbotCore.Leds,
FarmbotCore.LogExecutor,
FarmbotExt.AMQP.AutoSyncAssetHandler,
FarmbotExt.AMQP.ConnectionWorker,
FarmbotExt.API,
FarmbotExt.API.EagerLoader,
FarmbotExt.API.EagerLoader.Supervisor,
FarmbotExt.API.Preloader
]
|> Enum.map(&Mimic.copy/1)
timeout = System.get_env("EXUNIT_TIMEOUT")
timeout = System.get_env("EXUNIT_TIMEOUT") || "5000"
System.put_env("LOG_SILENCE", "true")
if timeout do
ExUnit.start(assert_receive_timeout: String.to_integer(timeout))
else
ExUnit.start()
end
ExUnit.start(assert_receive_timeout: String.to_integer(timeout))
defmodule Helpers do
# Maybe I don't need this?
# Maybe I could use `start_supervised`?
# https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#start_supervised/2
@wait_time 60
# Base case: We have a pid
def wait_for(pid) when is_pid(pid), do: check_on_mbox(pid)
# Failure case: We failed to find a pid for a module.
def wait_for(nil), do: raise("Attempted to wait on bad module/pid")
# Edge case: We have a module and need to try finding its pid.
def wait_for(mod), do: wait_for(Process.whereis(mod))
# Enter recursive loop
defp check_on_mbox(pid) do
Process.sleep(@wait_time)
wait(pid, Process.info(pid, :message_queue_len))
end
# Exit recursive loop (mbox is clear)
defp wait(_, {:message_queue_len, 0}), do: Process.sleep(@wait_time * 3)
# Exit recursive loop (pid is dead)
defp wait(_, nil), do: Process.sleep(@wait_time * 3)
# Continue recursive loop
defp wait(pid, {:message_queue_len, _n}), do: check_on_mbox(pid)
defmacro expect_log(message) do
quote do
expect(FarmbotCore.LogExecutor, :execute, fn log ->

View File

@ -6,6 +6,7 @@ defmodule FarmbotFirmware.CommandTest do
import ExUnit.CaptureLog
@subject FarmbotFirmware.Command
@tag :capture_log
test "command() runs RPCs" do
arg = [transport: FarmbotFirmware.StubTransport]
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
@ -19,6 +20,7 @@ defmodule FarmbotFirmware.CommandTest do
assert :ok == FarmbotFirmware.command(pid, cmd)
end
@tag :capture_log
test "command() refuses to run RPCs in :boot state" do
arg = [transport: FarmbotFirmware.StubTransport]
{:ok, pid} = FarmbotFirmware.start_link(arg, [])

View File

@ -4,6 +4,7 @@ defmodule FarmbotFirmware.UARTTransportTest do
doctest FarmbotFirmware.UARTTransport
alias FarmbotFirmware.{UartDefaultAdapter, UARTTransport}
setup :verify_on_exit!
import ExUnit.CaptureLog
test "UARTTransport.init/1" do
expect(UartDefaultAdapter, :start_link, fn ->
@ -61,15 +62,22 @@ defmodule FarmbotFirmware.UARTTransportTest do
fake_opts
end)
error = "Simulated UART failure. This is OK"
expect(UartDefaultAdapter, :open, fn _, _, _ ->
{:error, "Simulated UART failure. This is OK"}
{:error, error}
end)
{:noreply, state2, retry_timeout} =
UARTTransport.handle_info(:timeout, state)
logs =
capture_log(fn ->
{:noreply, state2, retry_timeout} =
UARTTransport.handle_info(:timeout, state)
assert retry_timeout == 5000
assert state.open == state2.open
assert retry_timeout == 5000
assert state.open == state2.open
end)
assert logs =~ error
end
test "UARTTransport handles `Circuits-UART` speecific errors" do

View File

@ -19,6 +19,7 @@ defmodule FarmbotFirmwareTest do
pid
end
@tag :capture_log
test "various reports" do
pid = firmware_server()
@ -63,6 +64,7 @@ defmodule FarmbotFirmwareTest do
Process.sleep(1000)
end
@tag :capture_log
test "various command()s" do
pid = firmware_server()

View File

@ -49,6 +49,9 @@ defmodule FarmbotFirmware.PackageUtilsTest do
{:ok, path} = PackageUtils.find_hex_file("express_k10")
assert String.contains?(path, "/farmbot_firmware/priv/express_k10.hex")
{:ok, path} = PackageUtils.find_hex_file("none")
assert path =~ "lib/farmbot_firmware/priv/eeprom_clear.ino.hex"
assert {:error, "unknown firmware hardware: no"} ==
PackageUtils.find_hex_file("no")
end

View File

@ -1,6 +1,11 @@
defmodule FarmbotFirmware.ParamTest do
use ExUnit.Case
alias FarmbotFirmware.Param
import ExUnit.CaptureLog
def t(p, v, expected) do
assert Param.to_human(p, v) == expected
end
test "to_human()" do
float_value = 1.23
@ -9,212 +14,354 @@ defmodule FarmbotFirmware.ParamTest do
steps_per_s = "(steps/s)"
steps_per_mm = "(steps/mm)"
assert Param.to_human(:param_test, 1) ==
{"param_test", nil, true}
assert Param.to_human(:param_config_ok, 1) ==
{"param_config_ok", nil, true}
assert Param.to_human(:param_use_eeprom, 1) ==
{"use eeprom", nil, true}
assert Param.to_human(:param_e_stop_on_mov_err, 1) ==
{"e-stop on movement errors", nil, true}
assert Param.to_human(:movement_timeout_x, float_value) ==
{"timeout after, x-axis", seconds, "1.2"}
assert Param.to_human(:movement_timeout_y, float_value) ==
{"timeout after, y-axis", seconds, "1.2"}
assert Param.to_human(:movement_timeout_z, float_value) ==
{"timeout after, z-axis", seconds, "1.2"}
assert Param.to_human(:movement_keep_active_x, 1) ==
{"always power motors, x-axis", nil, true}
assert Param.to_human(:movement_keep_active_y, 1) ==
{"always power motors, y-axis", nil, true}
assert Param.to_human(:movement_keep_active_z, 1) ==
{"always power motors, z-axis", nil, true}
assert Param.to_human(:movement_home_at_boot_x, 1) ==
{"find home on boot, x-axis", nil, true}
assert Param.to_human(:movement_home_at_boot_y, 1) ==
{"find home on boot, y-axis", nil, true}
assert Param.to_human(:movement_home_at_boot_z, 1) ==
{"find home on boot, z-axis", nil, true}
assert Param.to_human(:movement_invert_endpoints_x, 1) ==
{"swap endstops, x-axis", nil, true}
assert Param.to_human(:movement_invert_endpoints_y, 1) ==
{"swap endstops, y-axis", nil, true}
assert Param.to_human(:movement_invert_endpoints_z, 1) ==
{"swap endstops, z-axis", nil, true}
assert Param.to_human(:movement_enable_endpoints_x, 1) ==
{"enable endstops, x-axis", nil, true}
assert Param.to_human(:movement_enable_endpoints_y, 1) ==
{"enable endstops, y-axis", nil, true}
assert Param.to_human(:movement_enable_endpoints_z, 1) ==
{"enable endstops, z-axis", nil, true}
assert Param.to_human(:movement_invert_motor_x, 1) ==
{"invert motor, x-axis", nil, true}
assert Param.to_human(:movement_invert_motor_y, 1) ==
{"invert motor, y-axis", nil, true}
assert Param.to_human(:movement_invert_motor_z, 1) ==
{"invert motor, z-axis", nil, true}
assert Param.to_human(:movement_secondary_motor_x, 1) ==
{"enable 2nd x motor", nil, true}
assert Param.to_human(:movement_secondary_motor_invert_x, 1) ==
{"invert 2nd x motor", nil, true}
assert Param.to_human(:movement_stop_at_home_x, 1) ==
{"stop at home, x-axis", nil, true}
assert Param.to_human(:movement_stop_at_home_y, 1) ==
{"stop at home, y-axis", nil, true}
assert Param.to_human(:movement_stop_at_home_z, 1) ==
{"stop at home, z-axis", nil, true}
assert Param.to_human(:movement_step_per_mm_x, float_value) ==
{"steps per mm, x-axis", steps_per_mm, "1.2"}
assert Param.to_human(:movement_step_per_mm_y, float_value) ==
{"steps per mm, y-axis", steps_per_mm, "1.2"}
assert Param.to_human(:movement_step_per_mm_z, float_value) ==
{"steps per mm, z-axis", steps_per_mm, "1.2"}
assert Param.to_human(:movement_min_spd_x, float_value) ==
{"minimum speed, x-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_min_spd_y, float_value) ==
{"minimum speed, y-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_min_spd_z, float_value) ==
{"minimum speed, z-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_home_spd_x, float_value) ==
{"homing speed, x-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_home_spd_y, float_value) ==
{"homing speed, y-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_home_spd_z, float_value) ==
{"homing speed, z-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_max_spd_x, float_value) ==
{"max speed, x-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_max_spd_y, float_value) ==
{"max speed, y-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_max_spd_z, float_value) ==
{"max speed, z-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_invert_2_endpoints_x, 1) ==
{"invert endstops, x-axis", nil, true}
assert Param.to_human(:movement_invert_2_endpoints_y, 1) ==
{"invert endstops, y-axis", nil, true}
assert Param.to_human(:movement_invert_2_endpoints_z, 1) ==
{"invert endstops, z-axis", nil, true}
assert Param.to_human(:encoder_enabled_x, 1) ==
{"enable encoders / stall detection, x-axis", nil, true}
assert Param.to_human(:encoder_enabled_y, 1) ==
{"enable encoders / stall detection, y-axis", nil, true}
assert Param.to_human(:encoder_enabled_z, 1) ==
{"enable encoders / stall detection, z-axis", nil, true}
assert Param.to_human(:encoder_type_x, float_value) ==
{"encoder type, x-axis", nil, "1.2"}
assert Param.to_human(:encoder_type_y, float_value) ==
{"encoder type, y-axis", nil, "1.2"}
assert Param.to_human(:encoder_type_z, float_value) ==
{"encoder type, z-axis", nil, "1.2"}
assert Param.to_human(:encoder_scaling_x, float_value) ==
{"encoder scaling, x-axis", nil, "1.2"}
assert Param.to_human(:encoder_scaling_y, float_value) ==
{"encoder scaling, y-axis", nil, "1.2"}
assert Param.to_human(:encoder_scaling_z, float_value) ==
{"encoder scaling, z-axis", nil, "1.2"}
assert Param.to_human(:encoder_missed_steps_decay_x, float_value) ==
{"missed step decay, x-axis", steps, "1.2"}
assert Param.to_human(:encoder_missed_steps_decay_y, float_value) ==
{"missed step decay, y-axis", steps, "1.2"}
assert Param.to_human(:encoder_missed_steps_decay_z, float_value) ==
{"missed step decay, z-axis", steps, "1.2"}
assert Param.to_human(:encoder_use_for_pos_x, 1) ==
{"use encoders for positioning, x-axis", nil, true}
assert Param.to_human(:encoder_use_for_pos_y, 1) ==
{"use encoders for positioning, y-axis", nil, true}
assert Param.to_human(:encoder_use_for_pos_z, 1) ==
{"use encoders for positioning, z-axis", nil, true}
assert Param.to_human(:encoder_invert_x, 1) ==
{"invert encoders, x-axis", nil, true}
assert Param.to_human(:encoder_invert_y, 1) ==
{"invert encoders, y-axis", nil, true}
assert Param.to_human(:encoder_invert_z, 1) ==
{"invert encoders, z-axis", nil, true}
assert Param.to_human(:movement_stop_at_max_x, 1) ==
{"stop at max, x-axis", nil, true}
assert Param.to_human(:movement_stop_at_max_y, 1) ==
{"stop at max, y-axis", nil, true}
assert Param.to_human(:movement_stop_at_max_z, 1) ==
{"stop at max, z-axis", nil, true}
assert Param.to_human(:pin_guard_1_active_state, 0) ==
{"pin guard 1 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_2_active_state, 0) ==
{"pin guard 2 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_3_active_state, 0) ==
{"pin guard 3 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_4_active_state, 0) ==
{"pin guard 4 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_5_active_state, 0) ==
{"pin guard 5 safe state", nil, "HIGH"}
t(:pin_guard_5_time_out, 12, {"pin guard 5 timeout", "(seconds)", "12"})
t(:pin_guard_5_pin_nr, 12, {"pin guard 5 pin number", nil, "12"})
t(:pin_guard_5_active_state, 0, {"pin guard 5 safe state", nil, "HIGH"})
t(:pin_guard_4_time_out, 12, {"pin guard 4 timeout", "(seconds)", "12"})
t(:pin_guard_4_pin_nr, 12, {"pin guard 4 pin number", nil, "12"})
t(:pin_guard_4_active_state, 0, {"pin guard 4 safe state", nil, "HIGH"})
t(:pin_guard_3_time_out, 1.0, {"pin guard 3 timeout", "(seconds)", "1"})
t(:pin_guard_3_pin_nr, 1.0, {"pin guard 3 pin number", nil, "1"})
t(:pin_guard_3_active_state, 0, {"pin guard 3 safe state", nil, "HIGH"})
t(:pin_guard_2_time_out, 1.0, {"pin guard 2 timeout", "(seconds)", "1"})
t(:pin_guard_2_pin_nr, 1.0, {"pin guard 2 pin number", nil, "1"})
t(:pin_guard_2_active_state, 0, {"pin guard 2 safe state", nil, "HIGH"})
t(:pin_guard_1_time_out, 1.0, {"pin guard 1 timeout", "(seconds)", "1"})
t(:pin_guard_1_pin_nr, 1.0, {"pin guard 1 pin number", nil, "1"})
t(:pin_guard_1_active_state, 0, {"pin guard 1 safe state", nil, "HIGH"})
t(:param_use_eeprom, 1, {"use eeprom", nil, true})
t(:param_test, 1, {"param_test", nil, true})
t(:param_mov_nr_retry, 1.0, {"max retries", nil, "1"})
t(:param_e_stop_on_mov_err, 1, {"e-stop on movement errors", nil, true})
t(:param_config_ok, 1, {"param_config_ok", nil, true})
t(:movement_stop_at_max_z, 1, {"stop at max, z-axis", nil, true})
t(:movement_stop_at_max_y, 1, {"stop at max, y-axis", nil, true})
t(:movement_stop_at_max_x, 1, {"stop at max, x-axis", nil, true})
t(:movement_stop_at_home_z, 1, {"stop at home, z-axis", nil, true})
t(:movement_stop_at_home_y, 1, {"stop at home, y-axis", nil, true})
t(:movement_stop_at_home_x, 1, {"stop at home, x-axis", nil, true})
t(:movement_secondary_motor_x, 1, {"enable 2nd x motor", nil, true})
t(:movement_secondary_motor_invert_x, 1, {"invert 2nd x motor", nil, true})
t(:movement_microsteps_z, float_value, {"microsteps, z-axis", nil, "1.2"})
t(:movement_microsteps_y, float_value, {"microsteps, y-axis", nil, "1.2"})
t(:movement_microsteps_x, float_value, {"microsteps, x-axis", nil, "1.2"})
t(:movement_keep_active_z, 1, {"always power motors, z-axis", nil, true})
t(:movement_keep_active_y, 1, {"always power motors, y-axis", nil, true})
t(:movement_keep_active_x, 1, {"always power motors, x-axis", nil, true})
t(:movement_invert_motor_z, 1, {"invert motor, z-axis", nil, true})
t(:movement_invert_motor_y, 1, {"invert motor, y-axis", nil, true})
t(:movement_invert_motor_x, 1, {"invert motor, x-axis", nil, true})
t(:movement_invert_endpoints_z, 1, {"swap endstops, z-axis", nil, true})
t(:movement_invert_endpoints_y, 1, {"swap endstops, y-axis", nil, true})
t(:movement_invert_endpoints_x, 1, {"swap endstops, x-axis", nil, true})
t(:movement_home_at_boot_z, 1, {"find home on boot, z-axis", nil, true})
t(:movement_home_at_boot_y, 1, {"find home on boot, y-axis", nil, true})
t(:movement_home_at_boot_x, 1, {"find home on boot, x-axis", nil, true})
t(:movement_enable_endpoints_z, 1, {"enable endstops, z-axis", nil, true})
t(:movement_enable_endpoints_y, 1, {"enable endstops, y-axis", nil, true})
t(:movement_enable_endpoints_x, 1, {"enable endstops, x-axis", nil, true})
t(:encoder_type_z, 1.2, {"encoder type, z-axis", nil, "1.2"})
t(:encoder_type_y, 1.2, {"encoder type, y-axis", nil, "1.2"})
t(:encoder_type_x, 1.2, {"encoder type, x-axis", nil, "1.2"})
t(:encoder_invert_z, 1, {"invert encoders, z-axis", nil, true})
t(:encoder_invert_y, 1, {"invert encoders, y-axis", nil, true})
t(:encoder_invert_x, 1, {"invert encoders, x-axis", nil, true})
t(
:movement_motor_current_x,
float_value,
{"motor current, x-axis", "(milliamps)", "1.2"}
)
t(
:movement_motor_current_y,
float_value,
{"motor current, y-axis", "(milliamps)", "1.2"}
)
t(
:movement_motor_current_z,
float_value,
{"motor current, z-axis", "(milliamps)", "1.2"}
)
t(
:movement_stall_sensitivity_x,
float_value,
{"stall sensitivity, x-axis", nil, "1.2"}
)
t(
:movement_stall_sensitivity_y,
float_value,
{"stall sensitivity, y-axis", nil, "1.2"}
)
t(
:movement_stall_sensitivity_z,
float_value,
{"stall sensitivity, z-axis", nil, "1.2"}
)
t(
:movement_timeout_x,
float_value,
{"timeout after, x-axis", seconds, "1.2"}
)
t(
:movement_timeout_y,
float_value,
{"timeout after, y-axis", seconds, "1.2"}
)
t(
:movement_timeout_z,
float_value,
{"timeout after, z-axis", seconds, "1.2"}
)
t(
:movement_step_per_mm_x,
float_value,
{"steps per mm, x-axis", steps_per_mm, "1.2"}
)
t(
:movement_step_per_mm_y,
float_value,
{"steps per mm, y-axis", steps_per_mm, "1.2"}
)
t(
:movement_step_per_mm_z,
float_value,
{"steps per mm, z-axis", steps_per_mm, "1.2"}
)
t(
:movement_min_spd_x,
float_value,
{"minimum speed, x-axis", steps_per_s, "1.2"}
)
t(
:movement_min_spd_y,
float_value,
{"minimum speed, y-axis", steps_per_s, "1.2"}
)
t(
:movement_min_spd_z,
float_value,
{"minimum speed, z-axis", steps_per_s, "1.2"}
)
t(
:movement_home_spd_x,
float_value,
{"homing speed, x-axis", steps_per_s, "1.2"}
)
t(
:movement_home_spd_y,
float_value,
{"homing speed, y-axis", steps_per_s, "1.2"}
)
t(
:movement_home_spd_z,
float_value,
{"homing speed, z-axis", steps_per_s, "1.2"}
)
t(
:movement_max_spd_x,
float_value,
{"max speed, x-axis", steps_per_s, "1.2"}
)
t(
:movement_max_spd_y,
float_value,
{"max speed, y-axis", steps_per_s, "1.2"}
)
t(
:movement_max_spd_z,
float_value,
{"max speed, z-axis", steps_per_s, "1.2"}
)
t(
:movement_invert_2_endpoints_x,
1,
{"invert endstops, x-axis", nil, true}
)
t(
:movement_invert_2_endpoints_y,
1,
{"invert endstops, y-axis", nil, true}
)
t(
:movement_invert_2_endpoints_z,
1,
{"invert endstops, z-axis", nil, true}
)
t(
:encoder_enabled_x,
1,
{"enable encoders / stall detection, x-axis", nil, true}
)
t(
:encoder_enabled_y,
1,
{"enable encoders / stall detection, y-axis", nil, true}
)
t(
:encoder_enabled_z,
1,
{"enable encoders / stall detection, z-axis", nil, true}
)
t(
:encoder_scaling_x,
float_value,
{"encoder scaling, x-axis", nil, "1.2"}
)
t(
:encoder_scaling_y,
float_value,
{"encoder scaling, y-axis", nil, "1.2"}
)
t(
:encoder_scaling_z,
float_value,
{"encoder scaling, z-axis", nil, "1.2"}
)
t(
:encoder_missed_steps_decay_x,
float_value,
{"missed step decay, x-axis", steps, "1.2"}
)
t(
:encoder_missed_steps_decay_y,
float_value,
{"missed step decay, y-axis", steps, "1.2"}
)
t(
:encoder_missed_steps_decay_z,
float_value,
{"missed step decay, z-axis", steps, "1.2"}
)
t(
:encoder_use_for_pos_x,
1,
{"use encoders for positioning, x-axis", nil, true}
)
t(
:encoder_use_for_pos_y,
1,
{"use encoders for positioning, y-axis", nil, true}
)
t(
:encoder_use_for_pos_z,
1,
{"use encoders for positioning, z-axis", nil, true}
)
t(
:movement_axis_nr_steps_z,
1.0,
{"axis length, z-axis", "(steps)", "1"}
)
t(
:movement_axis_nr_steps_y,
1.0,
{"axis length, y-axis", "(steps)", "1"}
)
t(
:movement_axis_nr_steps_x,
1.0,
{"axis length, x-axis", "(steps)", "1"}
)
t(
:movement_steps_acc_dec_x,
1.0,
{"accelerate for, x-axis", "(steps)", "1"}
)
t(
:movement_steps_acc_dec_y,
1.0,
{"accelerate for, y-axis", "(steps)", "1"}
)
t(
:movement_steps_acc_dec_z,
1.0,
{"accelerate for, z-axis", "(steps)", "1"}
)
t(
:movement_home_up_x,
1.0,
{"negative coordinates only, x-axis", nil, true}
)
t(
:movement_home_up_y,
1.0,
{"negative coordinates only, y-axis", nil, true}
)
t(
:movement_home_up_z,
1.0,
{"negative coordinates only, z-axis", nil, true}
)
t(
:encoder_missed_steps_max_x,
1.0,
{"max missed steps, x-axis", "(steps)", "1"}
)
t(
:encoder_missed_steps_max_y,
1.0,
{"max missed steps, y-axis", "(steps)", "1"}
)
t(
:encoder_missed_steps_max_z,
1.0,
{"max missed steps, z-axis", "(steps)", "1"}
)
end
test "Handling of uknown parameters" do
assert :unknown_parameter == Param.decode(-999)
log =
capture_log(fn ->
assert :unknown_parameter == Param.decode(-999)
end)
assert log =~ "unknown firmware parameter: -999"
end
end

View File

@ -43,3 +43,5 @@ config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
firmware_flash_attempt_threshold: 0
config :plug, :validate_header_keys_during_test, true
config :ex_unit, capture_logs: true

View File

@ -99,13 +99,6 @@ config :farmbot, FarmbotOS.Configurator,
config :farmbot, FarmbotOS.System,
system_tasks: FarmbotOS.Platform.Target.SystemTasks
config :nerves_hub,
client: FarmbotOS.Platform.Target.NervesHubClient,
remote_iex: true,
public_keys: [File.read!("priv/staging.pub"), File.read!("priv/prod.pub")]
config :nerves_hub, NervesHub.Socket, reconnect_interval: 5_000
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,

View File

@ -20,16 +20,7 @@ defmodule FarmbotOS.Configurator.LoggerSocket do
end
@impl :cowboy_websocket
def websocket_handle({:text, message}, state) do
case Jason.decode(message) do
{:ok, json} ->
websocket_handle({:json, json}, state)
_ ->
_ = Logger.debug("discarding info: #{message}")
{:ok, state}
end
end
def websocket_handle({:text, _}, state), do: {:ok, state}
@impl :cowboy_websocket
def websocket_info(:after_connect, state) do

View File

@ -3,12 +3,28 @@ defmodule FarmbotOS.Configurator.LoggerSocketTest do
use Mimic
alias FarmbotOS.Configurator.LoggerSocket
setup :verify_on_exit!
import ExUnit.CaptureLog
test "init/2" do
# TODO(Rick) Not sure what the real args are.
# Circle back to make this test more realistic
# later.
expected = {:cowboy_websocket, :foo, :bar}
assert expected == LoggerSocket.init(:foo, :bar)
end
test "websocket_init" do
assert {:ok, %{}} == LoggerSocket.websocket_init(nil)
assert_receive :after_connect
end
test "websocket_handle (invalid JSON)" do
s = %{state: :yep}
msg = "Not JSON."
payl = {:text, msg}
assert {:ok, s} == LoggerSocket.websocket_handle(payl, s)
end
test "websocket_info/2" do
assert capture_log(fn ->
LoggerSocket.websocket_info(:whatever, %{})
end) =~ "Dropping :whatever"
end
end

View File

@ -8,6 +8,8 @@ defmodule FarmbotOS.Configurator.RouterTest do
use Mimic
setup :verify_on_exit!
import ExUnit.CaptureIO
@opts Router.init([])
# Stolen from https://github.com/phoenixframework/phoenix/blob/3f157c30ceae8d1eb524fdd05b5e3de10e434c42/lib/phoenix/test/conn_test.ex#L438
defp redirected_to(conn, status \\ 302)
@ -34,6 +36,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
Router.call(conn, @opts)
end
@tag :capture_log
test "index after reset" do
FarmbotOS.Configurator.ConfigDataLayer
|> expect(:load_last_reset_reason, fn -> "whoops!" end)
@ -45,6 +48,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "whoops!"
end
@tag :capture_log
test "redirects" do
redirects = [
"/check_network_status.txt",
@ -66,6 +70,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
end)
end
@tag :capture_log
test "celeryscript requests don't get listed as last reset reason" do
FarmbotOS.Configurator.ConfigDataLayer
|> expect(:load_last_reset_reason, fn -> "CeleryScript request." end)
@ -75,6 +80,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
refute conn.resp_body =~ "CeleryScript request."
end
@tag :capture_log
test "no reset reason" do
FarmbotOS.Configurator.ConfigDataLayer
|> expect(:load_last_reset_reason, fn -> nil end)
@ -84,6 +90,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
refute conn.resp_body =~ "<div class=\"last-shutdown-reason\">"
end
@tag :capture_log
test "captive portal" do
conn = conn(:get, "/generate_204")
conn = Router.call(conn, @opts)
@ -94,6 +101,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.status == 302
end
@tag :capture_log
test "network index" do
FarmbotOS.Configurator.FakeNetworkLayer
|> expect(:list_interfaces, fn ->
@ -108,6 +116,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "eth0"
end
@tag :capture_log
test "select network sets session data" do
conn = conn(:post, "select_interface")
conn = Router.call(conn, @opts)
@ -124,6 +133,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert get_session(conn, "ifname") == "wlan0"
end
@tag :capture_log
test "config wired" do
conn =
conn(:get, "/config_wired")
@ -133,6 +143,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "Advanced settings"
end
@tag :capture_log
test "config wireless SSID list" do
FarmbotOS.Configurator.FakeNetworkLayer
|> expect(:scan, fn _ ->
@ -154,6 +165,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "Test Network"
end
@tag :capture_log
test "config wireless" do
# No SSID or SECURITY
conn =
@ -238,6 +250,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "unknown or unsupported"
end
@tag :capture_log
test "config_network" do
params = %{
"dns_name" => "super custom",
@ -290,6 +303,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert redirected_to(conn) == "/credentials"
end
@tag :capture_log
test "credentials index" do
FarmbotOS.Configurator.ConfigDataLayer
|> expect(:load_email, fn -> "test@test.org" end)
@ -302,6 +316,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "https://my.farm.bot"
end
@tag :capture_log
test "configure credentials" do
params = %{
"email" => "test@test.org",
@ -334,6 +349,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert redirected_to(conn) == "/credentials"
end
@tag :capture_log
test "finish" do
conn =
conn(:get, "/finish")
@ -342,6 +358,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert redirected_to(conn) == "/"
end
@tag :capture_log
test "404" do
conn =
conn(:get, "/whoops")
@ -350,6 +367,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body == "Page not found"
end
@tag :capture_log
test "500" do
FarmbotOS.Configurator.FakeNetworkLayer
|> expect(:scan, fn _ ->
@ -360,20 +378,26 @@ defmodule FarmbotOS.Configurator.RouterTest do
]
end)
conn =
conn(:get, "/config_wireless")
|> init_test_session(%{"ifname" => "wlan0"})
|> Router.call(@opts)
crasher = fn ->
conn =
conn(:get, "/config_wireless")
|> init_test_session(%{"ifname" => "wlan0"})
|> Router.call(@opts)
assert conn.status == 500
assert conn.status == 500
end
assert capture_io(:stderr, crasher) =~ "render error"
end
@tag :capture_log
test "/scheduler_debugger" do
kon = get_con("/scheduler_debugger")
assert String.contains?(kon.resp_body, "scheduler_debugger.js")
assert String.contains?(kon.resp_body, "<title>Scheduler Debugger</title>")
end
@tag :capture_log
test "/logger" do
kon = get_con("/logger")
@ -391,6 +415,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
end)
end
@tag :capture_log
test "/api/telemetry/cpu_usage" do
{:ok, json} = Jason.decode(get_con("/api/telemetry/cpu_usage").resp_body)
assert Enum.count(json) == 10
@ -400,6 +425,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert(is_integer(zero["value"]))
end
@tag :capture_log
test "/finish" do
expect(ConfigDataLayer, :save_config, 1, fn _conf ->
:ok

View File

@ -4,6 +4,7 @@ defmodule FarmbotOS.LuaTest do
setup :verify_on_exit!
alias FarmbotOS.Lua
@tag :capture_log
test "evaluates Lua" do
assert Lua.eval_assertion("Returns 'true'", "return true")
{:error, message1} = Lua.eval_assertion("Returns 'true'", "-1")

View File

@ -11,6 +11,7 @@ defmodule FarmbotOS.SysCallsTest do
use Mimic
setup :verify_on_exit!
import ExUnit.CaptureIO
test "emergency_unlock" do
expect(FarmbotFirmware, :command, fn {:command_emergency_unlock, []} ->
@ -71,6 +72,7 @@ defmodule FarmbotOS.SysCallsTest do
assert {:error, "Could not find peripheral by id: 11"} == result6
end
@tag :capture_log
test "sync() success" do
# Expect 5 calls and an :ok response.
expect(FarmbotExt.API.Reconciler, :sync_group, 5, fn changeset, _group ->
@ -81,16 +83,21 @@ defmodule FarmbotOS.SysCallsTest do
{:ok, %{wut: module}}
end)
assert :ok == SysCalls.sync()
assert capture_io(fn ->
assert :ok == SysCalls.sync()
end) =~ "green really_fast_blink"
end
@tag :capture_log
test "sync() failure" do
# Expect 5 calls and an :ok response.
expect(FarmbotExt.API, :get_changeset, fn FarmbotCore.Asset.Sync ->
"this is a test"
end)
assert {:error, "\"this is a test\""} == SysCalls.sync()
assert capture_io(fn ->
assert {:error, "\"this is a test\""} == SysCalls.sync()
end) =~ "green slow_blink"
end
test "get_sequence(id)" do

View File

@ -91,6 +91,7 @@ defmodule FarmbotOS.SysCalls.MovementTest do
assert msg == error_log
end
@tag :capture_log
test "move_absolute/4 - error (in tuple)" do
expect(FarmbotFirmware, :request, 1, fn {:parameter_read, [_]} ->
{:error, "boom"}

View File

@ -6,6 +6,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
alias FarmbotCore.Asset.Peripheral
@digital 0
@tag :capture_log
test "read_pin with %Peripheral{}, pin is 1" do
expect(FarmbotFirmware, :request, 1, fn
{:pin_read, [p: 13, m: 0]} ->
@ -20,6 +21,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
assert 1 == PinControl.read_pin(peripheral, @digital)
end
@tag :capture_log
test "read_pin with %Peripheral{}, pin is 0" do
expect(FarmbotFirmware, :request, 1, fn
{:pin_read, [p: 13, m: 0]} ->
@ -30,6 +32,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
assert 0 == PinControl.read_pin(peripheral, @digital)
end
@tag :capture_log
test "toggle_pin, 1 => 0" do
expect(FarmbotCore.Asset, :get_peripheral_by_pin, 1, fn 12 ->
nil
@ -48,6 +51,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
assert :ok = PinControl.toggle_pin(12)
end
@tag :capture_log
test "toggle_pin, 0 => 1" do
expect(FarmbotCore.Asset, :get_peripheral_by_pin, 1, fn 12 ->
nil

View File

@ -101,6 +101,7 @@ defmodule FarmbotOS.SysCalls.PointLookupTest do
assert pg == PointLookup.get_point_group(pg.id)
end
@tag :capture_log
test "PointLookup.get_point_group/1 - string" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)

View File

@ -1,6 +1,4 @@
defmodule Farmbot.TestSupport.AssetFixtures do
alias FarmbotCore.Asset
alias FarmbotCore.Asset.{
Device,
FarmEvent,
@ -10,12 +8,12 @@ defmodule Farmbot.TestSupport.AssetFixtures do
Sequence
}
def regimen_instance(regimen_params, farm_event_params, params \\ %{}) do
regimen = regimen(regimen_params)
farm_event = regimen_event(regimen, farm_event_params)
params = Map.merge(%{id: :rand.uniform(10000), monitor: false}, params)
Asset.new_regimen_instance!(farm_event, params)
end
# def regimen_instance(regimen_params, farm_event_params, params \\ %{}) do
# regimen = regimen(regimen_params)
# farm_event = regimen_event(regimen, farm_event_params)
# params = Map.merge(%{id: :rand.uniform(10000), monitor: false}, params)
# Asset.new_regimen_instance!(farm_event, params)
# end
def fbos_config(params \\ %{}) do
default = %{
@ -77,30 +75,6 @@ defmodule Farmbot.TestSupport.AssetFixtures do
|> Repo.insert!()
end
def sequence_event(sequence, params \\ %{}) do
now = DateTime.utc_now()
params =
Map.merge(
%{
id: :rand.uniform(1_000_000),
monitor: false,
executable_type: "Sequence",
executable_id: sequence.id,
start_time: now,
end_time: now,
repeat: 0,
time_unit: "never"
},
params
)
FarmEvent
|> struct()
|> FarmEvent.changeset(params)
|> Repo.insert!()
end
@doc """
Instantiates, but does not create, a %Device{}
"""