Merge pull request #1650 from FarmBot/new_steps

(DRAFT) New Sequence Steps, Part II
log_problems_iii
Rick Carlino 2020-01-03 13:58:49 -06:00 committed by GitHub
commit a32d9f025b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 258 additions and 129 deletions

View File

@ -1 +1 @@
2.6.5
2.7.0

View File

@ -1,5 +1,5 @@
source "https://rubygems.org"
ruby "~> 2.6.5"
ruby "~> 2.7.0"
gem "rails"
gem "active_model_serializers"
@ -24,6 +24,7 @@ gem "scenic"
gem "secure_headers"
gem "tzinfo" # For validation of user selected timezone names
gem "valid_url"
# gem "farady", "~> 1.0.0"
group :development, :test do
gem "climate_control"

View File

@ -109,7 +109,7 @@ GEM
factory_bot_rails (5.1.1)
factory_bot (~> 5.1.0)
railties (>= 4.2.0)
faker (2.9.0)
faker (2.10.0)
i18n (>= 1.6, < 1.8)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
@ -119,7 +119,7 @@ GEM
railties (>= 3.2, < 6.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
google-api-client (0.36.1)
google-api-client (0.36.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
@ -178,13 +178,13 @@ GEM
passenger (6.0.4)
rack
rake (>= 0.8.1)
pg (1.1.4)
pg (1.2.1)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.1)
public_suffix (4.0.2)
rabbitmq_http_api_client (1.12.0)
faraday (~> 0.15.4)
faraday_middleware (~> 0.13.0)
@ -193,7 +193,7 @@ GEM
rack (2.0.8)
rack-attack (6.2.2)
rack (>= 1.0, < 3)
rack-cors (1.1.0)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-test (1.1.0)
rack (>= 1.0, < 3)
@ -245,12 +245,12 @@ GEM
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.0)
rspec-support (~> 3.9.0)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-rails (4.0.0.beta3)
@ -261,7 +261,7 @@ GEM
rspec-expectations (~> 3.8)
rspec-mocks (~> 3.8)
rspec-support (~> 3.8)
rspec-support (3.9.0)
rspec-support (3.9.2)
scenic (1.5.1)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
@ -285,7 +285,7 @@ GEM
sprockets (>= 3.0.0)
thor (1.0.1)
thread_safe (0.3.6)
tzinfo (1.2.5)
tzinfo (1.2.6)
thread_safe (~> 0.1)
uber (0.1.0)
url (0.3.2)
@ -341,7 +341,7 @@ DEPENDENCIES
valid_url
RUBY VERSION
ruby 2.6.5p114
ruby 2.7.0p0
BUNDLED WITH
1.17.2
2.1.3

View File

@ -26,7 +26,7 @@ module CeleryScript
def maybe_initialize(parent, leaf_or_node, key = NEVER)
if is_node?(leaf_or_node)
AstNode.new(parent, leaf_or_node)
AstNode.new(parent, **leaf_or_node)
else
raise TypeCheckError, LEAVES_NEED_KEYS if key == NEVER
AstLeaf.new(parent, leaf_or_node, key)

View File

@ -78,7 +78,7 @@ class Fragment < ApplicationRecord
def self.from_celery(device:, kind:, args:, body:, owner:)
p = { device: device, kind: kind, args: args, body: body }
flat_ast = Fragments::Preprocessor.run!(p)
flat_ast = Fragments::Preprocessor.run!(**p)
Fragments::Create.run!(device: device,
flat_ast: flat_ast,
owner: owner)

View File

@ -43,7 +43,7 @@ class Image < ApplicationRecord
has_one_attached :attachment
def set_attachment_by_url(url)
attachment.attach(io: open(url), filename: "image_#{self.id}")
attachment.attach(io: URI.open(url), filename: "image_#{self.id}")
self.attachment_processed_at = Time.now
self
end

View File

@ -16,7 +16,7 @@ module FarmEvents
kind: "internal_#{kind}",
args: {},
body: body }
flat_ast = Fragments::Preprocessor.run!(params)
flat_ast = Fragments::Preprocessor.run!(**params)
Fragments::Create.run!(device: device,
flat_ast: flat_ast,
owner: owner)

View File

@ -5,7 +5,7 @@ module Fragments
def self.run!(kind:, args:, body:, device:)
canonical = {kind: kind, args: args, body: body}
slicer = CeleryScript::Slicer.new
tree = CeleryScript::AstNode.new(canonical.deep_symbolize_keys)
tree = CeleryScript::AstNode.new(**canonical.deep_symbolize_keys)
checker = CeleryScript::Checker.new(tree, Corpus, device)
checker.run!
slicer.run!(canonical)

View File

@ -1,4 +1,4 @@
FROM ruby:2.6.5
FROM ruby:2.7.0
RUN wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | apt-key add - && \
sh -c 'VERSION_CODENAME=stretch; . /etc/os-release; echo "deb http://apt.postgresql.org/pub/repos/apt/ $VERSION_CODENAME-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' && \
apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql postgresql-contrib && \

View File

@ -1,13 +1,14 @@
jest.mock("../../api/crud", () => {
return { editStep: jest.fn() };
});
import { TileReboot, editTheRebootStep } from "../step_tiles/tile_reboot";
import { render } from "enzyme";
import { TileReboot, editTheRebootStep, rebootExecutor, MultiChoiceRadio } from "../step_tiles/tile_reboot";
import { render, mount } from "enzyme";
import React from "react";
import { StepParams } from "../interfaces";
import { fakeSequence } from "../../__test_support__/fake_state/resources";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { editStep } from "../../api/crud";
import { Reboot } from "farmbot";
const fakeProps = (): StepParams => {
const currentSequence = fakeSequence();
@ -55,4 +56,31 @@ describe("<TileReboot/>", () => {
executor: expect.any(Function),
});
});
it("executes the executor", () => {
const props = fakeProps();
const step = props.currentStep as Reboot;
step.args.package = "X";
const fn = rebootExecutor("arduino_firmware");
fn(step);
expect(step.args.package).toBe("arduino_firmware");
});
});
describe("MultiChoiceRadio", () => {
it("triggers callbacks", () => {
const PACKAGE_CHOICES = {
"a": "1",
"b": "2"
};
const onChange = jest.fn();
const el = mount(<MultiChoiceRadio
uuid={"WHATEVER"}
choices={PACKAGE_CHOICES}
currentChoice={"a"}
onChange={onChange} />);
const radio = el.find("input[type='radio']").first();
radio.simulate("change", "a");
expect(onChange).toHaveBeenCalledWith("a");
});
});

View File

@ -55,7 +55,7 @@ export function StepButtonCluster(props: StepButtonProps) {
pin_number: 4
}
}}
color="yellow">
color="orange">
{t("TOGGLE PERIPHERAL")}
</StepButton>,
<StepButton {...commonStepProps}
@ -103,7 +103,7 @@ export function StepButtonCluster(props: StepButtonProps) {
<StepButton {...commonStepProps}
step={{ kind: "emergency_lock", args: {} }}
color="brown">
{t("EMERGENCY LOCK")}
{t("E-STOP")}
</StepButton>,
<StepButton {...commonStepProps}
step={{ kind: "reboot", args: { package: "farmbot_os" } }}

View File

@ -1,10 +1,13 @@
jest.mock("../../../api/crud", () => ({ editStep: jest.fn() }));
import * as React from "react";
import { TileSetServoAngle } from "../tile_set_servo_angle";
import { TileSetServoAngle, pinNumberChanger, createServoEditFn, ServoPinSelection } from "../tile_set_servo_angle";
import { mount } from "enzyme";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { SetServoAngle } from "farmbot";
import { emptyState } from "../../../resources/reducer";
import { StepParams } from "../../interfaces";
import { editStep } from "../../../api/crud";
describe("<TileSetServoAngle/>", () => {
const currentStep: SetServoAngle = {
@ -18,20 +21,64 @@ describe("<TileSetServoAngle/>", () => {
const fakeProps = (): StepParams => ({
currentSequence: fakeSequence(),
currentStep: currentStep,
dispatch: jest.fn(),
dispatch: jest.fn((fn: Function) => typeof fn === "function" && fn()),
index: 0,
resources: emptyState().index,
confirmStepDeletion: false,
});
it("renders inputs", () => {
const block = mount(<TileSetServoAngle {...fakeProps()} />);
const props = fakeProps();
const block = mount(<TileSetServoAngle {...props} />);
const inputs = block.find("input");
const labels = block.find("label");
expect(inputs.length).toEqual(3);
expect(labels.length).toEqual(2);
expect(inputs.first().props().placeholder).toEqual("Set Servo Angle");
expect(labels.at(0).text()).toContain("Servo pin");
expect(inputs.at(1).props().value).toEqual(4);
const stepArgs = props.currentStep.args as SetServoAngle["args"];
expect(inputs.length).toEqual(4);
expect(labels.length).toEqual(4);
expect(inputs.first().props().placeholder).toEqual("Control Servo");
expect(labels.at(0).text()).toContain("Servo angle (0-180)");
expect(inputs.at(1).props().value).toEqual(stepArgs.pin_value);
expect(inputs.at(2).props().value).toEqual("" + stepArgs.pin_number);
});
it("Changes pin number", () => {
const props = fakeProps();
const fn = pinNumberChanger(props);
fn("5");
expect(editStep).toHaveBeenCalledWith({
step: props.currentStep,
sequence: props.currentSequence,
index: props.index,
executor: expect.any(Function)
});
});
it("dissallows named_pins", () => {
const p = fakeProps();
const step = p.currentStep;
if (step.kind === "set_servo_angle") {
step.args.pin_number = {
kind: "named_pin",
args: { pin_id: 0, pin_type: "Peripheral" }
};
const boom = () => ServoPinSelection(p);
expect(boom).toThrowError();
return;
}
fail();
});
it("creates a servo edit function", () => {
const props = fakeProps();
const step = props.currentStep;
const fn = createServoEditFn("4");
if (step.kind === "set_servo_angle") {
step.args.pin_number = 1;
fn(step);
expect(step.args.pin_number).toEqual(4);
} else {
fail();
}
});
});

View File

@ -27,10 +27,9 @@ describe("<TileTogglePin/>", () => {
const block = mount(<TileTogglePin {...fakeProps()} />);
const inputs = block.find("input");
const labels = block.find("label");
expect(inputs.length).toEqual(2);
expect(inputs.length).toEqual(1);
expect(labels.length).toEqual(1);
expect(inputs.first().props().placeholder).toEqual("Toggle Pin");
expect(labels.at(0).text()).toContain("Pin");
expect(inputs.at(1).props().value).toEqual(13);
expect(inputs.first().props().placeholder).toEqual("Toggle Peripheral");
expect(labels.at(0).text()).toContain("Peripheral");
});
});

View File

@ -2,35 +2,36 @@ const mockEditStep = jest.fn();
jest.mock("../../../api/crud", () => ({ editStep: mockEditStep }));
import * as React from "react";
import { TileWritePin, WritePinStep } from "../tile_write_pin";
import { TileWritePin, WritePinStep, PinSelect } from "../tile_write_pin";
import { mount, shallow } from "enzyme";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { WritePin } from "farmbot/dist";
import { emptyState } from "../../../resources/reducer";
import { FBSelect } from "../../../ui";
import { StepParams } from "../../interfaces";
function fakeProps() {
const currentStep: WritePin = {
kind: "write_pin",
args: {
pin_number: 3,
pin_value: 2,
pin_mode: 1
}
};
return {
currentSequence: fakeSequence(),
currentStep: currentStep,
dispatch: jest.fn(),
index: 0,
resources: emptyState().index,
confirmStepDeletion: false,
shouldDisplay: () => false,
showPins: false,
};
}
describe("<TileWritePin/>", () => {
function fakeProps() {
const currentStep: WritePin = {
kind: "write_pin",
args: {
pin_number: 3,
pin_value: 2,
pin_mode: 1
}
};
return {
currentSequence: fakeSequence(),
currentStep: currentStep,
dispatch: jest.fn(),
index: 0,
resources: emptyState().index,
confirmStepDeletion: false,
shouldDisplay: () => false,
showPins: false,
};
}
it("renders inputs: Analog", () => {
const wrapper = mount(<TileWritePin {...fakeProps()} />);
const inputs = wrapper.find("input");
@ -91,3 +92,12 @@ describe("<TileWritePin/>", () => {
.toThrow("Not a write_pin block.");
});
});
describe("<PinSelect/>", () => {
it("crashes on bad step `kind`s", () => {
const props = fakeProps() as StepParams;
props.currentStep = { kind: "execute", args: { sequence_id: 23 } };
const boom = () => PinSelect(props);
expect(boom).toThrowError("PinSelect can't render execute");
});
});

View File

@ -210,6 +210,7 @@ export const setArgsDotPinNumber =
switch (c.kind) {
case "read_pin":
case "write_pin":
case "toggle_pin":
c.args.pin_number = dropDown2CeleryArg(resources, d);
}
}

View File

@ -21,7 +21,7 @@ function translate(input: Step): string {
"take_photo": t("Take a Photo"),
"resource_update": t("Mark As"),
"assertion": t("Assertion"),
"set_servo_angle": t("Set Servo Angle"),
"set_servo_angle": t("Control Servo"),
"wait": t("Wait"),
"write_pin": t("Control Peripheral"),
"sync": t("Sync"),
@ -39,7 +39,7 @@ function translate(input: Step): string {
"home": t("Move to Home"),
"factory_reset": t("Factory reset"),
"reboot": t("Reboot"),
"toggle_pin": t("Toggle Pin"),
"toggle_pin": t("Toggle Peripheral"),
"zero": t("Set zero"),
"set_user_env": t("Set Farmware Env"),
};

View File

@ -16,7 +16,7 @@ interface MultiChoiceRadioProps<T extends StringMap> {
onChange(key: keyof T): void;
}
const MultiChoiceRadio =
export const MultiChoiceRadio =
<T extends StringMap>(props: MultiChoiceRadioProps<T>) => {
const choices = Object.keys(props.choices);
return <Row>
@ -74,7 +74,7 @@ export const editTheRebootStep =
export function TileReboot(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
const className = "set-zero-step";
const className = "take-photo-step";
assertReboot(currentStep);
return <StepWrapper>
<StepHeader

View File

@ -5,6 +5,47 @@ import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { Row, Col } from "../../ui/index";
import { t } from "../../i18next_wrapper";
import { MultiChoiceRadio } from "./tile_reboot";
import { SetServoAngle } from "farmbot";
import { editStep } from "../../api/crud";
const PACKAGE_CHOICES: Record<string, string> = {
"4": "Pin 4",
"5": "Pin 5",
};
type Keys =
| "dispatch"
| "currentStep"
| "currentSequence"
| "index";
type Props = Pick<StepParams, Keys>;
export const createServoEditFn = (y: string) => (x: SetServoAngle) => {
x.args.pin_number = parseInt(y, 10);
};
export const pinNumberChanger = (props: Props) => (y: string) => {
props.dispatch(editStep({
step: props.currentStep,
sequence: props.currentSequence,
index: props.index,
executor: createServoEditFn(y)
}));
};
export function ServoPinSelection(props: Props) {
const { currentSequence, index, currentStep } = props;
const num = (currentStep as SetServoAngle).args.pin_number;
if (typeof num !== "number") { throw new Error("NO!"); }
const onChange = pinNumberChanger(props);
return <MultiChoiceRadio
uuid={currentSequence.uuid + index}
choices={PACKAGE_CHOICES}
currentChoice={"" + num}
onChange={onChange} />;
}
export function TileSetServoAngle(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
@ -20,24 +61,21 @@ export function TileSetServoAngle(props: StepParams) {
confirmStepDeletion={props.confirmStepDeletion} />
<StepContent className={className}>
<Row>
<Col xs={12}>
<label>{t("Servo pin")}</label>
<StepInputBox dispatch={dispatch}
step={currentStep}
sequence={currentSequence}
index={index}
field={"pin_number"} />
</Col>
</Row>
<Row>
<Col xs={12}>
<label>{t("Servo angle (0-180)")}</label>
<StepInputBox dispatch={dispatch}
<Col lg={4} xs={6}>
<label>
{t("Servo angle (0-180)")}
</label>
<StepInputBox
dispatch={dispatch}
step={currentStep}
sequence={currentSequence}
index={index}
field={"pin_value"} />
</Col>
<Col lg={8} xs={6}>
<label>{t("Servo pin")}</label>
<ServoPinSelection {...props} />
</Col>
</Row>
</StepContent>
</StepWrapper>;

View File

@ -1,14 +1,13 @@
import * as React from "react";
import { StepInputBox } from "../inputs/step_input_box";
import { StepParams } from "../interfaces";
import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { Row, Col } from "../../ui/index";
import { t } from "../../i18next_wrapper";
import { Row } from "../../ui/index";
import { PinSelect } from "./tile_write_pin";
export function TileTogglePin(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
const className = "toggle-pin-step";
const className = "write-pin-step";
return <StepWrapper>
<StepHeader
className={className}
@ -20,14 +19,7 @@ export function TileTogglePin(props: StepParams) {
confirmStepDeletion={props.confirmStepDeletion} />
<StepContent className={className}>
<Row>
<Col xs={12}>
<label>{t("Pin")}</label>
<StepInputBox dispatch={dispatch}
step={currentStep}
sequence={currentSequence}
index={index}
field={"pin_number"} />
</Col>
<PinSelect {...props} width={6} />
</Row>
</StepContent>
</StepWrapper>;

View File

@ -44,22 +44,35 @@ export interface WritePinStepParams {
showPins: boolean;
}
export class WritePinStep extends React.Component<WritePinStepParams> {
interface PinSelectProps extends StepParams {
width?: number;
}
PinSelect = (): JSX.Element => {
const { currentSequence, resources, showPins } = this.props;
const { pin_number } = this.props.currentStep.args;
return <Col xs={6} md={6}>
export const PinSelect = (props: PinSelectProps): JSX.Element => {
const step = props.currentStep;
if (
step.kind === "write_pin" ||
step.kind === "toggle_pin" ||
step.kind === "read_pin") {
const { currentSequence, resources, showPins } = props;
const { pin_number } = step.args;
const width = props.width || 6;
return <Col xs={width} md={width}>
<label>{t("Peripheral")}</label>
<FBSelect
key={JSON.stringify(currentSequence)}
selectedItem={celery2DropDown(pin_number, resources)}
customNullLabel={t("Select a peripheral")}
onChange={setArgsDotPinNumber(this.props)}
onChange={setArgsDotPinNumber(props)}
list={pinsAsDropDownsWritePin(resources, !!showPins)} />
</Col>;
}
throw new Error("PinSelect can't render " + step.kind);
};
export class WritePinStep extends React.Component<WritePinStepParams> {
PinValueField = (): JSX.Element => {
const { dispatch, currentStep, index, currentSequence } = this.props;
return (!(currentStep.args.pin_mode === 0) || currentStep.args.pin_value > 1)
@ -90,7 +103,7 @@ export class WritePinStep extends React.Component<WritePinStepParams> {
confirmStepDeletion={this.props.confirmStepDeletion} />
<StepContent className={className}>
<Row>
<this.PinSelect />
<PinSelect {...this.props} />
<PinMode {...this.props} />
<Col xs={6} md={3}>
<label>{t("set to")}</label>

View File

@ -116,8 +116,8 @@ namespace :api do
# 60 days is the current policy.
cutoff = 60.days.ago
# Download release data from github
string_page_1 = open("#{RELEASES_URL}?per_page=100&page=1").read
string_page_2 = open("#{RELEASES_URL}?per_page=100&page=2").read
string_page_1 = URI.open("#{RELEASES_URL}?per_page=100&page=1").read
string_page_2 = URI.open("#{RELEASES_URL}?per_page=100&page=2").read
data = JSON.parse(string_page_1).push(*JSON.parse(string_page_2))
.map { |x| x.slice(VERSION, TIMESTAMP, PRERELEASE) } # Only grab keys that matter
.reject { |x| x.fetch(VERSION).include?("-") } # Remove RC/Beta releases

View File

@ -14,7 +14,7 @@ FRACTION_DELIM = "/"
# Fetch JSON over HTTP. Rails probably already has a helper for this :shrug:
def open_json(url)
begin
JSON.parse(open(url).read)
JSON.parse(URI.open(url).read)
rescue *[OpenURI::HTTPError, SocketError] => exception
puts exception.message
return {}
@ -155,7 +155,8 @@ namespace :coverage do
"compare against a PR's base branch and would always return 0% change."
task run: :environment do
# Fetch current build coverage data from the HTML summary.
statements, branches, functions, lines = Nokogiri::HTML(open(COVERAGE_FILE_PATH))
statements, branches, functions, lines =
Nokogiri::HTML(URI.open(COVERAGE_FILE_PATH))
.css(CSS_SELECTOR)
.map(&:text)
.map { |x| x.split(FRACTION_DELIM).map(&:to_f) }

View File

@ -5,12 +5,12 @@ EXCLUDE = []
# Load package.json as JSON.
def load_package_json()
return JSON.parse(open(PACKAGE_JSON_FILE).read)
return JSON.parse(URI.open(PACKAGE_JSON_FILE).read)
end
# Save JSON to package.json.
def save_package_json(json)
open(PACKAGE_JSON_FILE, "w") { |file|
URI.open(PACKAGE_JSON_FILE, "w") { |file|
file.write(JSON.pretty_generate(json))
file.puts
}

View File

@ -51,7 +51,7 @@ describe Api::PasswordResetsController do
it "handles token expiration" do
token = PasswordResetToken
.issue_to(user, { exp: Time.now.yesterday.to_i })
.issue_to(user, **{ exp: Time.now.yesterday.to_i })
.encoded
params = { password: "xpassword123",

View File

@ -9,7 +9,7 @@ describe "Body nodes" do
let(:device) { FactoryBot.create(:device) }
it "always always empty bodies" do
tree = CeleryScript::AstNode.new({
tree = CeleryScript::AstNode.new(**{
"kind": "baz",
"args": {},
"body": []
@ -19,7 +19,7 @@ describe "Body nodes" do
end
it "kicks back unexpected nodes" do
tree = CeleryScript::AstNode.new({
tree = CeleryScript::AstNode.new(**{
"kind": "baz",
"args": {},
"body": [{ "kind": "wrong", "args": {}}]
@ -30,7 +30,7 @@ describe "Body nodes" do
end
it "handles body members of nodes that shouldn't have bodies." do
tree = CeleryScript::AstNode.new({
tree = CeleryScript::AstNode.new(**{
"kind": "baz",
"args": {},
"body": [{ "kind": "wrong", "args": {}}]
@ -41,7 +41,7 @@ describe "Body nodes" do
end
it 'disallows leaves in the body field of a node' do
tree = CeleryScript::AstNode.new({
tree = CeleryScript::AstNode.new(**{
"kind": "wrong",
"args": {},
"body": [

View File

@ -14,9 +14,7 @@ describe CeleryScript::Checker do
}.deep_symbolize_keys
end
let(:tree) do
CeleryScript::AstNode.new(hash)
end
let(:tree) { CeleryScript::AstNode.new(**hash) }
let (:corpus) { Sequence::Corpus }
@ -205,14 +203,14 @@ describe CeleryScript::Checker do
it "catches bad `axis` nodes" do
t =
CeleryScript::AstNode.new({ kind: "home", args: { speed: 100, axis: "?" } })
CeleryScript::AstNode.new(kind: "home", args: { speed: 100, axis: "?" })
chk = CeleryScript::Checker.new(t, corpus, device)
expect(chk.valid?).to be false
expect(chk.error.message).to include("not a valid axis")
end
it "catches bad `package` nodes" do
t = CeleryScript::AstNode.new({ kind: "factory_reset", args: { package: "?" } })
t = CeleryScript::AstNode.new(kind: "factory_reset", args: { package: "?" })
chk = CeleryScript::Checker.new(t, corpus, device)
expect(chk.valid?).to be false
expect(chk.error.message).to include("not a valid package")
@ -251,7 +249,7 @@ describe CeleryScript::Checker do
},
],
}
tree = CeleryScript::AstNode.new(ast)
tree = CeleryScript::AstNode.new(**ast)
chk = CeleryScript::Checker.new(tree, corpus, device)
expect(chk.valid?).to be true
end
@ -289,7 +287,7 @@ describe CeleryScript::Checker do
},
],
}
tree = CeleryScript::AstNode.new(ast)
tree = CeleryScript::AstNode.new(**ast)
chk = CeleryScript::Checker.new(tree, corpus, device)
expect(chk.valid?).to be false
message = "must provide a value for all parameters"

View File

@ -5,7 +5,7 @@ describe CeleryScript::Corpus do
let(:corpus) { Sequence::Corpus }
it "handles valid move_absolute blocks" do
ok1 = CeleryScript::AstNode.new({
ok1 = CeleryScript::AstNode.new(**{
kind: "move_absolute",
args: {
location: {
@ -30,7 +30,7 @@ describe CeleryScript::Corpus do
check1 = CeleryScript::Checker.new(ok1, corpus, device)
expect(check1.valid?).to be_truthy
ok2 = CeleryScript::AstNode.new({
ok2 = CeleryScript::AstNode.new(**{
kind: "move_absolute",
args: {
location: {
@ -53,7 +53,7 @@ describe CeleryScript::Corpus do
end
it "kicks back invalid move_absolute nodes" do
bad = CeleryScript::AstNode.new({
bad = CeleryScript::AstNode.new(**{
kind: "move_absolute",
args: {
location: 42,
@ -75,7 +75,7 @@ describe CeleryScript::Corpus do
end
it "finds problems with nested nodes" do
bad = CeleryScript::AstNode.new({
bad = CeleryScript::AstNode.new(**{
kind: "move_absolute",
args: {
location: {
@ -109,7 +109,7 @@ describe CeleryScript::Corpus do
it "Handles message_type validations for version 1" do
# This test is __ONLY__ relevant for version 1.
# Change / delete / update as needed.
tree = CeleryScript::AstNode.new({
tree = CeleryScript::AstNode.new(**{
"kind": "send_message",
"args": {
"message": "Hello, world!",
@ -122,7 +122,7 @@ describe CeleryScript::Corpus do
end
it "Handles channel_name validations" do
tree = CeleryScript::AstNode.new({
tree = CeleryScript::AstNode.new(**{
"kind": "send_message",
"args": {
"message": "Hello, world!",
@ -141,7 +141,7 @@ describe CeleryScript::Corpus do
it "validates tool_ids" do
ast = { "kind": "tool", "args": { "tool_id": 0 } }
checker = CeleryScript::Checker.new(CeleryScript::AstNode.new(ast),
checker = CeleryScript::Checker.new(CeleryScript::AstNode.new(**ast),
corpus,
device)
expect(checker.valid?).to be(false)
@ -154,7 +154,8 @@ describe CeleryScript::Corpus do
"resource_id" => 23, # Mutated to "0" later..
"label" => "mounted_tool_id",
"value" => 1 } }
checker = CeleryScript::Checker.new(CeleryScript::AstNode.new(ast), corpus, device)
checker = CeleryScript::Checker
.new(CeleryScript::AstNode.new(**ast), corpus, device)
expect(checker.valid?).to be(true)
expect(checker.tree.args[:resource_id].value).to eq(device.id)
end
@ -167,7 +168,7 @@ describe CeleryScript::Corpus do
"resource_id" => fake_id,
"label" => "foo",
"value" => "Should Fail" } }
hmm = CeleryScript::AstNode.new(ast)
hmm = CeleryScript::AstNode.new(**ast)
expect(hmm.args.fetch(:resource_id).value).to eq(fake_id)
checker = CeleryScript::Checker.new(hmm, corpus, device)
expect(checker.valid?).to be(false)
@ -180,7 +181,7 @@ describe CeleryScript::Corpus do
"resource_id" => 0,
"label" => "foo",
"value" => "Should Fail" } }
checker = CeleryScript::Checker.new(CeleryScript::AstNode.new(ast),
checker = CeleryScript::Checker.new(CeleryScript::AstNode.new(**ast),
corpus,
device)
expect(checker.valid?).to be(false)
@ -222,7 +223,7 @@ describe CeleryScript::Corpus do
end
it "sets a MAX_WAIT_MS limit for `wait` nodes" do
bad = CeleryScript::AstNode.new({
bad = CeleryScript::AstNode.new(**{
kind: "wait",
args: { milliseconds: CeleryScriptSettingsBag::MAX_WAIT_MS + 10 },
})
@ -236,7 +237,7 @@ describe CeleryScript::Corpus do
pg = PointGroups::Create.run!(device: device,
name: "cs checks",
point_ids: [])
bad = CeleryScript::AstNode.new({
bad = CeleryScript::AstNode.new(**{
kind: "point_group",
args: { point_group_id: pg.id },
})
@ -247,7 +248,7 @@ describe CeleryScript::Corpus do
it "disallows invalid `point_group` nodes" do
device.auto_sync_transaction do
bad = CeleryScript::AstNode.new({
bad = CeleryScript::AstNode.new(**{
kind: "point_group",
args: { point_group_id: -1 },
})

View File

@ -22,7 +22,7 @@ describe "Celery Script `point` node" do
}.deep_symbolize_keys
end
let(:tree) { CeleryScript::AstNode.new(hash) }
let(:tree) { CeleryScript::AstNode.new(**hash) }
let(:corpus) { Sequence::Corpus }
let(:device) { plant.device }
let(:checker) { CeleryScript::Checker.new(tree, corpus, device) }

View File

@ -5,7 +5,7 @@ describe CeleryScript::TreeClimber do
let(:node) do
hash = JSON.parse(file_path).deep_symbolize_keys
CeleryScript::AstNode.new(hash)
CeleryScript::AstNode.new(**hash)
end
it "travels to each node with a callable object" do

View File

@ -47,7 +47,7 @@ describe Fragments::Create do
},
],
}
flat_ast = Fragments::Preprocessor.run!(origin)
flat_ast = Fragments::Preprocessor.run!(**origin)
fragment = Fragments::Create.run!(flat_ast: flat_ast, owner: farm_event)
result = Fragments::Show.run!(owner: farm_event)
diff = Hashdiff.diff(origin.without(:device), result.deep_symbolize_keys)
@ -83,7 +83,7 @@ describe Fragments::Create do
config.logger = spy_logger
fragment = Fragments::Create.run!(device: device,
owner: farm_event,
flat_ast: Fragments::Preprocessor.run!(origin))
flat_ast: Fragments::Preprocessor.run!(**origin))
# Warm the cache up with two dry-runs:
Fragments::Show.run!(owner: farm_event)
Fragments::Show.run!(owner: farm_event)