294 lines
8.2 KiB
Ruby
294 lines
8.2 KiB
Ruby
require "spec_helper"
|
|
|
|
describe CeleryScript::Checker do
|
|
let(:device) { FactoryBot.create(:device) }
|
|
let(:hash) do
|
|
{
|
|
kind: "sequence",
|
|
args: {
|
|
locals: Sequence::SCOPE_DECLARATION,
|
|
version: 0,
|
|
},
|
|
comment: "Properly formatted, syntactically valid sequence.",
|
|
body: sequence_body_for(FakeSequence.create()),
|
|
}.deep_symbolize_keys
|
|
end
|
|
|
|
let(:tree) do
|
|
CeleryScript::AstNode.new(hash)
|
|
end
|
|
|
|
let (:corpus) { Sequence::Corpus }
|
|
|
|
let (:checker) { CeleryScript::Checker.new(tree, corpus, device) }
|
|
|
|
it "runs through a syntactically valid program" do
|
|
outcome = checker.run!
|
|
expect(outcome).to be_kind_of(CeleryScript::AstNode)
|
|
expect(outcome.comment).to eq("Properly formatted, syntactically valid" \
|
|
" sequence.")
|
|
end
|
|
|
|
it "handles missing args" do
|
|
tree.body.first.args[:location].args.delete(:x)
|
|
expect(checker.valid?).to be(false)
|
|
msg = checker.error.message
|
|
expect(msg).to include("Expected node 'coordinate' to have a 'x'")
|
|
end
|
|
|
|
it "handles unknown args" do
|
|
tree.body.first.args[:foo] = "bar"
|
|
expect(checker.valid?).to be(false)
|
|
msg = checker.error.message
|
|
expect(msg).to include("unexpected arguments: [:foo].")
|
|
end
|
|
|
|
it "handles malformed / wrong type args" do
|
|
tree.body.first.args[:location].args[:x] = "WRONG!"
|
|
expect(checker.valid?).to be(false)
|
|
msg = checker.error.message
|
|
expect(msg).to eq("Expected 'x' to be a node or leaf, but it was neither")
|
|
end
|
|
|
|
it "returns an error rather than raising one via #run()" do
|
|
outcome = checker.run
|
|
expect(outcome).to be_kind_of(CeleryScript::AstNode)
|
|
checker.tree.body.first.args[:x] = "No longer valid"
|
|
expect(checker.run).to be_kind_of(CeleryScript::TypeCheckError)
|
|
end
|
|
|
|
it "handles wrong leaf types" do
|
|
hash[:body][0][:args][:location][:args][:x] = "supposed to be an Integer"
|
|
result = checker.run
|
|
expect(result.message).to eq("Expected leaf 'x' within 'coordinate' to be " \
|
|
"one of: [Integer, Float] but got String")
|
|
end
|
|
|
|
it "finds a bad leaf" do
|
|
parent = CeleryScript::AstNode.new(parent = nil, args: {}, kind: "nothing")
|
|
expect {
|
|
checker.check_leaf CeleryScript::AstLeaf.new(parent, 6, :location)
|
|
}.to raise_error(CeleryScript::TypeCheckError)
|
|
end
|
|
|
|
it "validates subsequence presence" do
|
|
hash[:body] = [
|
|
{ kind: "execute", args: { sequence_id: 0 } },
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expect(chk.error.message).to eq("missing a sequence selection for `execute` block.")
|
|
end
|
|
|
|
it "validates peripheral presence" do
|
|
hash[:body] = [
|
|
{
|
|
kind: "read_pin",
|
|
args: {
|
|
pin_number: {
|
|
kind: "named_pin",
|
|
args: {
|
|
pin_type: "Peripheral",
|
|
pin_id: 0,
|
|
},
|
|
},
|
|
pin_mode: CeleryScriptSettingsBag::ANALOG,
|
|
label: "FOO",
|
|
},
|
|
},
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expect(chk.error.message).to eq("Peripheral requires a valid pin number")
|
|
end
|
|
|
|
it "Catches bad `pin_type`s in `read_pin`" do
|
|
hash[:body] = [
|
|
{
|
|
kind: "read_pin",
|
|
args: {
|
|
pin_mode: 0,
|
|
label: "pin",
|
|
pin_number: {
|
|
kind: "named_pin",
|
|
args: { pin_type: "Not correct", pin_id: 1 },
|
|
},
|
|
},
|
|
},
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expect(chk.error.message).to include("not a type of pin")
|
|
end
|
|
|
|
it "Catches bad `pin_type`s in `read_pin`" do
|
|
hash[:body] = [
|
|
{
|
|
kind: "read_pin",
|
|
args: {
|
|
pin_mode: CeleryScriptSettingsBag::ANALOG,
|
|
label: "pin",
|
|
pin_number: {
|
|
kind: "named_pin",
|
|
args: { pin_type: "Peripheral", pin_id: 900 },
|
|
},
|
|
},
|
|
},
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expect(chk.error.message).to include("Can't find Peripheral with id of 900")
|
|
end
|
|
|
|
it 'allows "BoxLed3", "BoxLed4" as `pin_type`s' do
|
|
hash[:body] = [
|
|
{
|
|
kind: "write_pin",
|
|
args: {
|
|
pin_value: 23,
|
|
pin_mode: 0,
|
|
pin_number: {
|
|
kind: "named_pin",
|
|
args: { pin_type: ["BoxLed3", "BoxLed4"].sample, pin_id: 41 },
|
|
},
|
|
},
|
|
},
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be true
|
|
end
|
|
|
|
it 'disallows analog for "BoxLed3", "BoxLed4"' do
|
|
hash[:body] = [
|
|
{
|
|
kind: "write_pin",
|
|
args: {
|
|
pin_value: 23,
|
|
pin_mode: CeleryScriptSettingsBag::ANALOG,
|
|
pin_number: {
|
|
kind: "named_pin",
|
|
args: { pin_type: ["BoxLed3", "BoxLed4"].sample, pin_id: 41 },
|
|
},
|
|
},
|
|
},
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expect(chk.error.message).to include(CeleryScriptSettingsBag::CANT_ANALOG)
|
|
end
|
|
|
|
it 'gives human-friendly names to "BoxLed3", "BoxLed4"' do
|
|
hash[:body] = [
|
|
{
|
|
kind: "write_pin",
|
|
args: {
|
|
pin_value: 23,
|
|
pin_mode: CeleryScriptSettingsBag::DIGITAL,
|
|
pin_number: {
|
|
kind: "named_pin",
|
|
args: { pin_type: ["BoxLed3", "BoxLed4"].sample, pin_id: 0 },
|
|
},
|
|
},
|
|
},
|
|
]
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expected =
|
|
CeleryScriptSettingsBag::NO_PIN_ID % CeleryScriptSettingsBag::BoxLed.name
|
|
expect(chk.error.message).to eq(expected)
|
|
end
|
|
|
|
it "catches bad `axis` nodes" do
|
|
t =
|
|
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: "?" } })
|
|
chk = CeleryScript::Checker.new(t, corpus, device)
|
|
expect(chk.valid?).to be false
|
|
expect(chk.error.message).to include("not a valid package")
|
|
end
|
|
|
|
it "handles good variable declarations" do
|
|
ast = {
|
|
kind: "sequence",
|
|
args: {
|
|
version: 20180209,
|
|
locals: {
|
|
kind: "scope_declaration",
|
|
:args => {},
|
|
body: [
|
|
{
|
|
kind: "parameter_declaration",
|
|
args: {
|
|
label: "parent",
|
|
default_value: {
|
|
kind: "coordinate",
|
|
args: { x: 0, y: 0, z: 0 },
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
body: [
|
|
{
|
|
kind: "move_absolute",
|
|
args: {
|
|
speed: 100,
|
|
location: { kind: "identifier", args: { label: "parent" } },
|
|
offset: { kind: "coordinate", args: { x: 0, y: 0, z: 0 } },
|
|
},
|
|
},
|
|
],
|
|
}
|
|
tree = CeleryScript::AstNode.new(ast)
|
|
chk = CeleryScript::Checker.new(tree, corpus, device)
|
|
expect(chk.valid?).to be true
|
|
end
|
|
|
|
it "handles bad variable declarations" do
|
|
ast = {
|
|
kind: "sequence",
|
|
args: {
|
|
version: 20180209,
|
|
locals: {
|
|
kind: "scope_declaration",
|
|
:args => {},
|
|
body: [
|
|
{
|
|
kind: "parameter_declaration",
|
|
args: {
|
|
label: "parent",
|
|
default_value: {
|
|
kind: "nothing",
|
|
args: {},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
body: [
|
|
{
|
|
kind: "move_absolute",
|
|
args: {
|
|
speed: 100,
|
|
location: { kind: "identifier", args: { label: "parent" } },
|
|
offset: { kind: "coordinate", args: { x: 0, y: 0, z: 0 } },
|
|
},
|
|
},
|
|
],
|
|
}
|
|
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"
|
|
expect(chk.error.message).to include(message)
|
|
end
|
|
end
|