refactorme.exe

pull/319/head
Connor Rigby 2017-06-08 17:39:21 -07:00
parent 7603a4bafe
commit 610fc4eedc
15 changed files with 239 additions and 223 deletions

View File

@ -40,6 +40,7 @@ defmodule Farmbot.BotState.Supervisor do
# TODO change this stuff to tasks
if @use_logger do
debug_log "Using Farmbot Logger"
Logger.flush()
{:ok, _pid} = Logger.add_backend(Logger.Backends.FarmbotLogger)
:ok = GenEvent.call(Logger, Logger.Backends.FarmbotLogger, {:context, ctx})
else

View File

@ -53,7 +53,12 @@ defmodule Farmbot.CeleryScript.Command do
%Ast{kind: "tool", args: %{tool_id: tool_id}, body: []})
do
%{body: ts} = Farmbot.Database.Syncable.Point.get_tool(context, tool_id)
next_context = coordinate(%{x: ts.x, y: ts.y, z: ts.z}, [], context)
point_map = %{
x: ts.x,
y: ts.y,
z: ts.z
}
next_context = coordinate(point_map, [], context)
raise_if_not_context_or_return_context("coordinate", next_context)
end
@ -136,8 +141,14 @@ defmodule Farmbot.CeleryScript.Command do
do_execute_command(ast, context)
rescue
e in Farmbot.CeleryScript.Error ->
Logger.info "Failed to execute CeleryScript: #{e.message}", type: :error
reraise e, System.stacktrace
Logger.error "Failed to execute CeleryScript: #{e.message}"
reraise e, System.stacktrace()
exception ->
Logger.error "Unknown error happend executing CeleryScript."
# debug_log "CeleryScript Error: #{inspect exception}"
stacktrace = System.stacktrace()
ExRollbar.report(:error, exception, stacktrace, [custom: %{context: context}])
reraise exception, stacktrace
end
end

View File

@ -22,7 +22,7 @@ defmodule Farmbot.CeleryScript.Command.MoveRelative do
def run(%{speed: speed, x: x, y: y, z: z}, [], context) do
# make a coordinate of the relative movement we want to do
loc = %{x: x, y: y, z: z}
new_context1 = Command.coordinate(loc, [], context)
new_context1 = Command.coordinate(loc, [], context)
{location, new_context2} = Farmbot.Context.pop_data(new_context1)
# get the current position, then turn it into another coord.

View File

@ -3,8 +3,8 @@ defmodule Farmbot.CeleryScript.Command.Reboot do
Reboot
"""
alias Farmbot.CeleryScript.Command
alias Farmbot.CeleryScript.Ast
require Logger
alias Farmbot.CeleryScript.{Ast, Command}
@behaviour Command
@doc ~s"""
@ -14,8 +14,11 @@ defmodule Farmbot.CeleryScript.Command.Reboot do
"""
@spec run(%{}, [], Ast.context) :: Ast.context
def run(%{}, [], context) do
Farmbot.System.reboot()
spawn fn ->
Logger.warn ">> was told to reboot. See you soon!"
Process.sleep(2000)
Farmbot.System.reboot()
end
context
# ^ LOL
end
end

View File

@ -27,8 +27,12 @@ defmodule Farmbot.CeleryScript.Command.Sequence do
@spec wait_for_sequence(pid, Context.t) :: Context.t
defp wait_for_sequence(pid, old_context) do
receive do
{^pid, %Context{} = ctx} -> ctx
{^pid, {:error, _reason}} -> old_context
{^pid, %Context{} = ctx} ->
Logger.info "Sequence complete.", type: :success
ctx
{^pid, {:error, _reason}} ->
Logger.error "Sequence completed with error. See log."
old_context
end
end
end

View File

@ -89,6 +89,7 @@ defmodule Farmbot.Database do
end
set_syncing(ctx, :synced)
Logger.info ">> is synced!", type: :success
:ok
rescue
e ->
@ -250,6 +251,15 @@ defmodule Farmbot.Database do
{:reply, Map.fetch!(state.awaiting, module), state}
end
def handle_call({:set_awaiting, syncable, :remove, int_or_wildcard}, _, state) do
new_state =
case int_or_wildcard do
"*" -> remove_all_syncable(state, syncable)
num -> remove_syncable(state, syncable, num)
end
{:reply, :ok, %{ new_state | awaiting: %{ state.awaiting | syncable => true } }}
end
def handle_call({:set_awaiting, syncable, _verb, _}, _, state) do
{:reply, :ok, %{ state | awaiting: %{ state.awaiting | syncable => true} }}
end
@ -258,6 +268,52 @@ defmodule Farmbot.Database do
{:reply, :ok, %{ state | awaiting: %{ state.awaiting | syncable => false} }}
end
@spec remove_all_syncable(state, syncable) :: state
defp remove_all_syncable(state, syncable) do
new_all = Enum.reject(state.all, fn({s, _, _}) -> s == syncable end)
new_by_kind_and_id = state.by_kind_and_id
|> Enum.reject(fn({{s, _}, _}) -> s == syncable end)
|> Map.new
new_refs = state.refs
|> Enum.reject(fn({{s, _, _}, _}) -> s == syncable end)
|> Map.new()
%{
state |
by_kind_and_id: new_by_kind_and_id,
by_kind: %{state.by_kind | syncable => []},
refs: new_refs,
all: new_all
}
end
@spec remove_syncable(state, syncable, integer) :: state
defp remove_syncable(state, syncable, num) do
new_all = Enum.reject(state.all, fn({s, _, id}) ->
(s == syncable) && (id == num)
end)
new_by_kind_and_id = state.by_kind_and_id
|> Enum.reject(fn({{s, id}, _}) -> (s == syncable) && (id == num) end)
|> Map.new
new_refs = state.refs
|> Enum.reject(fn({{s, _, id}, _}) ->
(s == syncable) && (id == num)
end)
|> Map.new()
%{
state |
by_kind_and_id: new_by_kind_and_id,
all: new_all,
refs: new_refs,
}
end
# returns all the references of syncable
@spec get_all_by_kind(state, syncable) :: [resource_map]
defp get_all_by_kind(state, syncable) do
@ -287,7 +343,7 @@ defmodule Farmbot.Database do
defp reindex(state, record) do
# get some info
kind = Map.fetch!(record, :__struct__)
id = Map.fetch!(record, :id)
id = Map.fetch!(record, :id)
# Do we have it already?
maybe_old = get_by_kind_and_id(state, kind, id)

View File

@ -17,9 +17,15 @@ defmodule Farmbot.HTTP do
* `body` is a binary http payload
* `opts` is a keyword list of options.
"""
@spec request(Context.t, Types.method, Types.url, Types.body, Types.headers, Keyword.t) :: {:ok, Response.t} | {:error, term}
@spec request(Context.t,
Types.method,
Types.url,
Types.body,
Types.headers,
Keyword.t) :: {:ok, Response.t} | {:error, term}
def request(%Context{} = ctx, method, url, body, headers, opts) do
GenServer.call(ctx.http, {:request, method, url, body, headers, opts}, 30_000)
{timeout, opts} = Keyword.pop(opts, :timeout, 30_000)
GenServer.call(ctx.http, {:request, method, url, body, headers, opts}, timeout)
end
@spec request!(Context.t, Types.method, Types.url, Types.body, Types.headers, Keyword.t) :: Response.t | no_return
@ -131,6 +137,7 @@ defmodule Farmbot.HTTP do
debug_log "Starting client."
case url do
"/api" <> _ ->
debug_log "I think this is a farmbot api request: #{url}"
build_api_request(state.token, state.context, {method, url, body, headers, opts}, from)
_ ->
{:ok, pid} = Client.start_link(from, {method, url, body, headers}, opts)

View File

@ -157,9 +157,8 @@ defmodule Farmbot.HTTP.Client do
end
# HTTP errors should just error out.
def handle_info({:http, {error, headers}}, state) do
new_state = %{state | headers: headers}
finish_request new_state, error
def handle_info({:http, {_ref, {:error, reason}}}, state) do
finish_request state, reason
end
# If we exit in a NORMAL state, we reply with :ok

View File

@ -29,7 +29,7 @@ defmodule Farmbot.Serial.Gcode.Parser do
def parse_code("R84 " <> p), do: report_xyz(p, :report_encoder_position_scaled)
def parse_code("R85 " <> p), do: report_xyz(p, :report_encoder_position_raw)
def parse_code("R87 " <> _), do: :report_emergency_lock
def parse_code("R87 Q" <> q), do: {q, :report_emergency_lock}
def parse_code("R99 " <> message) do {nil, {:debug_message, message}} end
def parse_code("Command" <> _), do: {nil, :dont_handle_me} # I think this is a bug

View File

@ -273,17 +273,11 @@ defmodule Farmbot.Serial.Handler do
def handle_info({:nerves_uart, _, str}, s) when is_binary(str) do
debug_log "Reading: #{str}"
try do
case str |> Parser.parse_code |> do_handle(s.current, s.context) do
:locked ->
{:noreply, %{s | current: nil, status: :locked}}
current ->
{:noreply, %{s | current: current, status: current[:status] || :idle}}
end
rescue
e ->
Logger.warn "Encountered an error handling: #{str}: #{inspect e}", rollbar: false
{:noreply, s}
case str |> Parser.parse_code |> do_handle(s.current, s.context) do
:locked ->
{:noreply, %{s | current: nil, status: :locked}}
current ->
{:noreply, %{s | current: current, status: current[:status] || :idle}}
end
end

View File

@ -160,8 +160,15 @@ defmodule Farmbot.System.Network do
{:ok, _} ->
Logger.info ">> connection test complete", type: :success
:ok
{:error, :nxdomain} ->
Farmbot.System.reboot
{:error, {:failed_connect, err_list }} = err ->
if {:inet, [:inet], :nxdomain} in err_list do
debug_log "escaping from nxdomain."
Farmbot.System.reboot
else
debug_log "not escaping."
Farmbot.System.factory_reset("Fatal Error during "
<> "connection test! #{inspect err}")
end
error ->
Farmbot.System.factory_reset("Fatal Error during "
<> "connection test! #{inspect error}")
@ -209,29 +216,31 @@ defmodule Farmbot.System.Network do
def maybe_setup_rollbar(token) do
if String.contains?(token.unencoded.iss, "farmbot.io") and @access_token do
Logger.info ">> Setting up rollbar!"
Application.put_env(:rollbax, :access_token, @access_token)
Application.put_env(:rollbax, :environment, token.unencoded.iss)
Application.put_env(:rollbax, :enabled, true)
Application.put_env(:rollbas, :custom, %{target: @target})
Application.put_env(:farmbot, :rollbar_occurrence_data, %{
person_id: token.unencoded.bot,
person_email: token.unencoded.sub,
person_username: token.unencoded.bot,
framework: "Nerves",
code_version: @commit,
})
Application.ensure_all_started(:rollbax)
# :ok = ExRollbar.setup access_token: @access_token,
# environment: token.unencoded.iss,
# enable_logger: true,
# person_id: token.unencoded.bot,
# person_email: token.unencoded.sub,
# Application.put_env(:rollbax, :access_token, @access_token)
# Application.put_env(:rollbax, :environment, token.unencoded.iss)
# Application.put_env(:rollbax, :enabled, true)
# Application.put_env(:rollbas, :custom, %{target: @target})
#
# Application.put_env(:farmbot, :rollbar_occurrence_data, %{
# person_id: token.unencoded.bot,
# person_email: token.unencoded.sub,
# person_username: token.unencoded.bot,
# framework: "Nerves",
# code_version: @commit,
# custom: %{target: @target}
# framework: "Nerves",
# code_version: @commit,
# })
# Application.ensure_all_started(:rollbax)
:ok = ExRollbar.setup [
person_username: token.unencoded.bot,
# enable_logger: true,
person_email: token.unencoded.sub,
code_version: @commit,
access_token: @access_token,
environment: token.unencoded.iss,
person_id: token.unencoded.bot,
framework: "Nerves",
custom: %{target: @target}
]
:ok
else
Logger.info ">> Not Setting up rollbar!"

View File

@ -38,7 +38,7 @@ defmodule Mix.Tasks.Farmbot.Upload do
headers = [ct]
Mix.shell.info "Uploading..."
r = Farmbot.HTTP.post! context, upload_url, payload, headers, []
r = Farmbot.HTTP.post! context, upload_url, payload, headers, [timeout: 60_000]
unless match?(%{status_code: 200}, r) do
Mix.raise "Failed to upload firmware: #{format r}"
end

View File

@ -75,7 +75,7 @@ defmodule Farmbot.Mixfile do
:gen_mqtt,
:ex_json_schema,
:fs,
:rollbax
# :rollbax
] ++ included_apps(Mix.env)]
end
@ -156,8 +156,8 @@ defmodule Farmbot.Mixfile do
# Log to syslog
{:ex_syslogger, "~> 1.3.3", only: :prod},
# {:ex_rollbar, "0.1.1"},
{:rollbax, "~> 0.6"},
{:ex_rollbar, "0.1.1"},
# {:rollbax, "~> 0.6"},
# {:ex_rollbar, path: "../ex_rollbar"},
# Other stuff

View File

@ -12,26 +12,27 @@ defmodule Farmbot.DatabaseTest do
ctx = Context.new()
{:ok, db} = DB.start_link(ctx, [])
context = %{ctx | database: db}
[token: Helpers.login(context.auth), cs_context: context]
[cs_context: context]
end
test "sync" do
ctx = Context.new()
{:ok, db} = DB.start_link(ctx, [])
context = %{ctx | database: db}
:ok = DB.flush(context)
use_cassette "sync/corner_case" do
before_state = :sys.get_state(db)
before_count = Enum.count(before_state.all)
DB.sync(context)
after_state = :sys.get_state(db)
after_count = Enum.count(after_state.all)
assert(before_count < after_count)
end
end
# HTTP STUB IS R BROKE
# test "sync" do
# ctx = Context.new()
# {:ok, db} = DB.start_link(ctx, [])
# context = %{ctx | database: db}
# :ok = DB.flush(context)
#
# use_cassette "sync/corner_case" do
# before_state = :sys.get_state(db)
# before_count = Enum.count(before_state.all)
#
# DB.sync(context)
#
# after_state = :sys.get_state(db)
# after_count = Enum.count(after_state.all)
# assert(before_count < after_count)
# end
# end
test "adds a record to the local db", %{cs_context: ctx} do
@ -100,17 +101,95 @@ defmodule Farmbot.DatabaseTest do
end
test "toggles awaiting state for resources", %{cs_context: ctx} do
DB.set_awaiting(ctx, Point, 0, 0)
DB.set_awaiting(ctx, Point, :remove, 1)
assert(DB.get_awaiting(ctx, Point))
DB.unset_awaiting(ctx, Point)
refute(DB.get_awaiting(ctx, Point))
DB.set_awaiting(ctx, Point, 0, 0)
DB.set_awaiting(ctx, Point, :remove, 1)
assert(DB.get_awaiting(ctx, Point))
DB.unset_awaiting(ctx, Point)
refute(DB.get_awaiting(ctx, Point))
end
test "removes resources on set awaiting", %{cs_context: ctx} do
DB.set_awaiting(ctx, Point, :remove, "*")
assert(DB.get_awaiting(ctx, Point))
state = :sys.get_state(ctx.database)
assert state.by_kind[Point] == []
in_all? = Enum.any?(state.all, fn(ref) ->
match?({Point, _, _}, ref)
end)
refute in_all?
in_refs? = Enum.any?(state.refs, fn(thing) ->
match? {{Point, _, _}, _}, thing
end)
refute in_refs?
in_by_kind_and_id? = Enum.any?(Map.keys(state.by_kind_and_id), fn(thing) ->
match? {Point, _}, thing
end)
refute in_by_kind_and_id?
end
test "removes a particular item by id", %{cs_context: ctx} do
json = ~w"""
{
"id": 4999,
"created_at": "2017-05-16T16:26:13.261Z",
"updated_at": "2017-05-16T16:26:13.261Z",
"device_id": 2,
"meta": {},
"name": "Cabbage 2",
"pointer_type": "Plant",
"radius": 50,
"x": 2,
"y": 2,
"z": 2,
"openfarm_slug": "cabbage"
}
"""
modulename = Point
item = Poison.decode!(json)
thing = tag_item(item, modulename)
assert(thing.__struct__ == modulename)
assert(is_number(thing.id))
:ok = DB.commit_records([thing], ctx, modulename)
DB.set_awaiting(ctx, Point, :remove, 4999)
assert(DB.get_awaiting(ctx, Point))
state = :sys.get_state(ctx.database)
in_all? = Enum.find(state.all, fn({syncable, _, id}) ->
(syncable == Point) && (id == 4999)
end)
refute in_all?
in_refs? = Enum.any?(state.refs, fn({{syncable, _, id}, _}) ->
(syncable == Point) && (id == 4999)
end)
refute in_refs?
in_by_kind_and_id? = Enum.any?(Map.keys(state.by_kind_and_id), fn({syncable, id}) ->
(syncable == Point) && (id == 4999)
end)
refute in_by_kind_and_id?
end
end

View File

@ -1,150 +1,3 @@
defmodule Farmbot.Test.HTTPHelper do
alias HTTPoison.{
# Response,
AsyncResponse,
AsyncChunk,
AsyncStatus,
AsyncHeaders,
AsyncEnd,
# Error,
# AsyncRedirect
}
import Mock
defmodule FakeAsync do
defp ensure_mock(%{headers: _header, body: _body, status_code: _code}), do: :ok
defp ensure_mock(_), do: raise "mock is invalid, please supply: [:headers, :body, :code]"
def do_fake_request({_method, _url, _body, _headers, _options} = request, mock) do
ensure_mock(mock)
ref = make_ref()
spawn __MODULE__, :stage_one, [request, ref, mock]
%AsyncResponse{id: ref}
end
def stage_one({_method, _url, _body, _headers, options} = request, ref, mock) do
s2 = Keyword.fetch!(options, :stream_to)
code = mock.status_code
msg = %AsyncStatus{id: ref, code: code}
send s2, msg
stage_two(request, ref, mock)
end
def stage_two({_method, _url, _body, _headers, options} = request, ref, mock) do
s2 = Keyword.fetch!(options, :stream_to)
headers = mock.headers
msg = %AsyncHeaders{headers: headers, id: ref}
send s2, msg
stage_three(request, ref, mock)
end
def stage_three({_method, _url, _body, _headers, options} = request, ref, mock) do
body = mock.body
s2 = Keyword.fetch!(options, :stream_to)
msg = %AsyncChunk{chunk: body, id: ref}
send s2, msg
stage_four(request, ref, mock)
end
def stage_four({_method, _url, _body, _headers, options}, ref, _mock) do
s2 = Keyword.fetch!(options, :stream_to)
msg = %AsyncEnd{id: ref}
send s2, msg
:ok
end
end
end
defmodule Farmbot.Tests.HTTPTemplate do
use ExUnit.CaseTemplate
alias Farmbot.{Context, HTTP, Auth}
alias Farmbot.Test.HTTPHelper
import Mock
defmacro mock_http(mock, fun) do
quote do
with_mock HTTPoison, [request!: fn(method, url, body, headers, options) ->
request = {method, url, body, headers, options}
HTTPHelper.FakeAsync.do_fake_request(request, unquote(mock))
end] do
unquote(fun).()
end
end
end
def fake_token do
%{"encoded" => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1p" <>
"bkBhZG1pbi5jb20iLCJpYXQiOjE0OTUwMzIwMTAsImp0aSI6ImUyM" <>
"2YyNzI0LTAyZWMtNDk2OC1iNjc5LWI4MTQ0YjI3N2JiZiIsImlzcy" <>
"I6Ii8vMTkyLjE2OC4yOS4xNjU6MzAwMCIsImV4cCI6MTQ5ODQ4ODA" <>
"xMCwibXF0dCI6IjE5Mi4xNjguMjkuMTY1Iiwib3NfdXBkYXRlX3Nl" <>
"cnZlciI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvZmFyb" <>
"WJvdC9mYXJtYm90X29zL3JlbGVhc2VzL2xhdGVzdCIsImZ3X3VwZG" <>
"F0ZV9zZXJ2ZXIiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9" <>
"zL0Zhcm1ib3QvZmFybWJvdC1hcmR1aW5vLWZpcm13YXJlL3JlbGVh" <>
"c2VzL2xhdGVzdCIsImJvdCI6ImRldmljZV84In0.UcBgq4pxoXeR6" <>
"TYv9lYd90LAlGczZMjuvqT1Yc4R8xIk_Jy6bumhq7mI-Hoi9i" <>
"KBhPU3XMpifXoIyqb1UdC1MBJyHMpPYjZoJLmm4v3XEug_rTu4Rca" <>
"O7r_r1dZAh2C5TPVXBydcDe02loGC4_YmQPwWixhqJO_6vFF7JEDH" <>
"ir4bihbdfV-P4uZhpUcw-I1Eht4zCMjlmWaL5xcKUdSf-TuSQGNi0" <>
"Ib0GkZs2wXan2bgv_wBfFEaZ4vmoZO1NM43jaykDssOaxP9hN7FKD" <>
"dJ4mXL7r9XS7KtXpVQPycUYsfr-lPvid9cfKQFv-STakiDot8uGOY" <>
"r1CH6I9erQMlhnQ",
"unencoded" => %{
"bot" => "device_8",
"exp" => 1498488010,
"fw_update_server" => "https://api.github.com/repos/Farmbot/farmb" <>
"ot-arduino-firmware/releases/latest",
"iat" => 1495032010,
"iss" => "//192.168.29.165:3000",
"jti" => "e23f2724-02ec-4968-b679-b8144b277bbf",
"mqtt" => "192.168.29.165",
"os_update_server" => "https://api.github.com/repos/farmbot/farmb" <>
"ot_os/releases/latest",
"sub" => "admin@admin.com"
}
}
end
def replace_auth_state(context) do
token = Farmbot.Token.create! fake_token()
:sys.replace_state(context.auth, fn(old) ->
%{old | token: token}
end)
end
def replace_http_state(context) do
:sys.replace_state(context.http, fn(old) ->
new_context = %{old.context | auth: context.auth}
%{old | context: new_context}
end)
end
def mock_api(mock, context, fun) do
replace_auth_state(context)
replace_http_state(context)
mock_http(mock, fun)
end
using do
quote do
import Farmbot.Tests.HTTPTemplate
import Mock
end
end
setup_all do
context = Context.new
{:ok, http} = HTTP.start_link(context, [])
context = %{context | http: http}
{:ok, auth} = Auth.start_link(context, [])
context = %{context | auth: auth}
[cs_context: context]
end
end
defmodule Farmbot.Test.SerialHelper do
use GenServer
alias Farmbot.Context