Fix last_error behaviour (#66)

This commit is contained in:
vms 2021-02-16 20:04:00 +03:00 committed by GitHub
parent c6d8a458da
commit 271156b5fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 413 additions and 41 deletions

1
Cargo.lock generated
View File

@ -17,6 +17,7 @@ dependencies = [
"codespan-reporting",
"criterion",
"fstrings",
"itertools 0.10.0",
"lalrpop",
"lalrpop-util",
"regex",

View File

@ -18,6 +18,8 @@ codespan-reporting = "0.9.5"
serde = { version = "=1.0.118", features = ["rc"] }
serde_json = "=1.0.61"
itertools = "0.10.0"
thiserror = "1.0.23"
[dev-dependencies]

View File

@ -1,3 +1,21 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
mod traits;
pub use crate::parser::lexer::Number;
use serde::Deserialize;

View File

@ -0,0 +1,189 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use super::*;
use std::fmt;
impl fmt::Display for CallInstrArgValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallInstrArgValue::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
LastError => write!(f, "%last_error%"),
Literal(str) => write!(f, r#""{}""#, str),
Number(number) => write!(f, "{}", number),
Boolean(bool) => write!(f, "{}", bool),
Variable(str) => write!(f, "{}", str),
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
}
}
}
impl fmt::Display for CallInstrValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallInstrValue::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
Literal(str) => write!(f, r#""{}""#, str),
Variable(str) => write!(f, "{}", str),
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
}
}
}
impl fmt::Display for IterableValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use IterableValue::*;
match self {
Variable(str) => write!(f, "{}", str),
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
}
}
}
impl fmt::Display for MatchableValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use MatchableValue::*;
match self {
Literal(str) => write!(f, r#""{}""#, str),
Number(number) => write!(f, "{}", number),
Boolean(bool) => write!(f, "{}", bool),
Variable(str) => write!(f, "{}", str),
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
}
}
}
impl fmt::Display for CallOutputValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallOutputValue::*;
match self {
Scalar(str) => write!(f, "{}", str),
Accumulator(str) => write!(f, "{}[]", str),
None => Ok(()),
}
}
}
impl fmt::Display for PeerPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use PeerPart::*;
match self {
PeerPk(peer_pk) => write!(f, "{}", peer_pk),
PeerPkWithServiceId(peer_pk, service_id) => write!(f, "({} {})", peer_pk, service_id),
}
}
}
impl fmt::Display for FunctionPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FunctionPart::*;
match self {
FuncName(func_name) => write!(f, "{}", func_name),
ServiceIdWithFuncName(service_id, func_name) => {
write!(f, "({} {})", service_id, func_name)
}
}
}
}
impl fmt::Display for Instruction<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Instruction::*;
match self {
Null(null) => write!(f, "{}", null),
Call(call) => write!(f, "{}", call),
Seq(seq) => write!(f, "{}", seq),
Par(par) => write!(f, "{}", par),
Xor(xor) => write!(f, "{}", xor),
Match(match_) => write!(f, "{}", match_),
MisMatch(mismatch) => write!(f, "{}", mismatch),
Fold(fold) => write!(f, "{}", fold),
Next(next) => write!(f, "{}", next),
Error => Ok(()),
}
}
}
impl fmt::Display for Call<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use itertools::Itertools;
write!(f, "call {} {}", self.peer_part, self.function_part)?;
let args = self.args.iter().map(|arg| format!("{}", arg)).join(" ");
write!(f, " [{}]", args)?;
write!(f, " {}", self.output)
}
}
impl fmt::Display for Fold<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fold {} {}", self.iterable, self.iterator)
}
}
impl fmt::Display for Seq<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "seq")
}
}
impl fmt::Display for Par<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "par")
}
}
impl fmt::Display for Null {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "null")
}
}
impl fmt::Display for Xor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "xor")
}
}
impl fmt::Display for Match<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "match {} {}", self.left_value, self.right_value)
}
}
impl fmt::Display for MisMatch<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mismatch {} {}", self.left_value, self.right_value)
}
}
impl fmt::Display for Next<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "next")
}
}

View File

@ -51,6 +51,19 @@ pub enum Number {
Float(f64),
}
use std::fmt;
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Number::*;
match self {
Int(number) => write!(f, "{}", number),
Float(number) => write!(f, "{}", number),
}
}
}
impl From<Number> for Token<'_> {
fn from(value: Number) -> Self {
Token::Number(value)

View File

@ -136,6 +136,33 @@ pub fn set_variables_call_service(ret_mapping: HashMap<String, String>) -> CallS
})
}
pub fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServiceClosure {
let fallible_service_id = fallible_service_id.into();
Box::new(move |_, args| -> Option<IValue> {
let builtin_service = match &args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
// return a error for service with such id
if builtin_service == &fallible_service_id {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(1), IValue::String(String::from("error"))]).unwrap(),
))
} else {
// return success for services with other ids
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(String::from(r#""res""#)),
])
.unwrap(),
))
}
})
}
#[macro_export]
macro_rules! call_vm {
($vm:expr, $init_peer_id:expr, $script:expr, $prev_data:expr, $data:expr) => {

View File

@ -50,6 +50,10 @@ pub(crate) struct ExecutionCtx<'i> {
/// None means that there weren't any error.
pub last_error: Option<LastErrorDescriptor>,
/// True, if last error could be set. This flag is used to distinguish
/// whether an error is being bubbled up from the bottom or just encountered.
pub last_error_could_be_set: bool,
/// Indicates that previous executed subtree is complete.
/// A subtree treats as a complete if all subtree elements satisfy the following rules:
/// - at least one of par subtrees is completed
@ -101,6 +105,7 @@ impl<'i> ExecutionCtx<'i> {
current_peer_id,
init_peer_id,
subtree_complete: true,
last_error_could_be_set: true,
..<_>::default()
}
}

View File

@ -30,6 +30,8 @@ use crate::SecurityTetraplet;
use air_parser::ast::Call;
use std::rc::Rc;
/// This macro converts joinable errors to Ok and sets subtree complete to true.
macro_rules! joinable {
($cmd:expr, $exec_ctx:expr) => {
@ -48,25 +50,32 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
log_instruction!(call, exec_ctx, trace_ctx);
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx).map_err(|e| {
let instruction = format!("{:?}", self);
let last_error = LastErrorDescriptor::new(e.clone(), instruction, None);
exec_ctx.last_error = Some(last_error);
set_last_error(self, exec_ctx, e.clone(), None);
e
})?;
let triplet = resolved_call.as_triplet();
joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx).map_err(|e| {
let tetraplet = SecurityTetraplet::from_triplet(triplet);
let instruction = format!("{:?}", self);
let last_error = LastErrorDescriptor::new(e.clone(), instruction, Some(tetraplet));
exec_ctx.last_error = Some(last_error);
set_last_error(self, exec_ctx, e.clone(), Some(tetraplet));
e
})
}
}
fn set_last_error<'i>(
call: &Call<'i>,
exec_ctx: &mut ExecutionCtx<'i>,
e: Rc<ExecutionError>,
tetraplet: Option<SecurityTetraplet>,
) {
let instruction = format!("{}", call);
let last_error = LastErrorDescriptor::new(e, instruction, tetraplet);
exec_ctx.last_error = Some(last_error);
exec_ctx.last_error_could_be_set = false;
}
macro_rules! log_join {
($($args:tt)*) => {
log::trace!(target: crate::log_targets::JOIN_BEHAVIOUR, $($args)*)

View File

@ -39,7 +39,12 @@ macro_rules! execute {
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {
match $instr.execute($exec_ctx, $trace_ctx) {
Err(e) => {
let instruction = format!("{:?}", $self);
if !$exec_ctx.last_error_could_be_set {
return Err(e);
}
let instruction = format!("{}", $self);
println!("set error on {}", instruction);
let last_error = LastErrorDescriptor::new(e.clone(), instruction, None);
$exec_ctx.last_error = Some(last_error);
Err(e)

View File

@ -30,6 +30,8 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> {
match self.0.execute(exec_ctx, trace_ctx) {
Err(e) if is_catchable_by_xor(&e) => {
exec_ctx.subtree_complete = true;
exec_ctx.last_error_could_be_set = true;
self.1.execute(exec_ctx, trace_ctx)
}
res => res,
@ -55,35 +57,10 @@ mod tests {
use aqua_test_utils::call_vm;
use aqua_test_utils::create_aqua_vm;
use aqua_test_utils::CallServiceClosure;
use aqua_test_utils::IValue;
use aqua_test_utils::NEVec;
use aqua_test_utils::fallible_call_service;
use std::rc::Rc;
fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServiceClosure {
let fallible_service_id = fallible_service_id.into();
Box::new(move |_, args| -> Option<IValue> {
let builtin_service = match &args[0] {
IValue::String(str) => str,
_ => unreachable!(),
};
// return a error for service with such id
if builtin_service == &fallible_service_id {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(1), IValue::String(String::from("error"))]).unwrap(),
))
} else {
// return success for services with other ids
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(String::from(r#""res""#))]).unwrap(),
))
}
})
}
#[test]
fn xor() {
use crate::contexts::execution_trace::CallResult::*;
@ -265,7 +242,7 @@ mod tests {
let actual_trace: ExecutionTrace = serde_json::from_slice(&res.data).expect("should be valid json");
let expected_state = Call(Executed(Rc::new(JValue::String(String::from(
"{\"error\":\"Local service error: ret_code is 1, error message is \'error\'\",\"instruction\":\"Call { peer_part: PeerPk(Literal(\\\"failible_peer_id\\\")), function_part: ServiceIdWithFuncName(Literal(\\\"service_id_1\\\"), Literal(\\\"local_fn_name\\\")), args: [], output: Scalar(\\\"result\\\") }\"}"
"{\"error\":\"Local service error: ret_code is 1, error message is \'error\'\",\"instruction\":\"call \\\"failible_peer_id\\\" (\\\"service_id_1\\\" \\\"local_fn_name\\\") [] result\"}"
)))));
assert_eq!(actual_trace[1], expected_state);

View File

@ -0,0 +1,81 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use aqua_test_utils::call_vm;
use aqua_test_utils::create_aqua_vm;
use aqua_test_utils::fallible_call_service;
use aqua_test_utils::unit_call_service;
use aqua_test_utils::CallServiceClosure;
use aqua_test_utils::IValue;
use aqua_test_utils::NEVec;
use stepper_lib::SecurityTetraplet;
fn create_check_service_closure() -> CallServiceClosure {
Box::new(move |_, args| -> Option<IValue> {
let call_args = match &args[2] {
IValue::String(str) => str,
_ => unreachable!(),
};
let call_args: Vec<String> = serde_json::from_str(call_args).expect("json deserialization shouldn't fail");
let tetraplets = match &args[3] {
IValue::String(str) => str,
_ => unreachable!(),
};
let de_tetraplets: Vec<Vec<SecurityTetraplet>> =
serde_json::from_str(tetraplets).expect("json deserialization shouldn't fail");
assert_eq!(
call_args[0],
r#"{"error":"Local service error: ret_code is 1, error message is 'error'","instruction":"call \"failible_peer_id\" ("falliable_call_service" "") [service_id] client_result"}"#
);
let triplet = &de_tetraplets[0][0].triplet;
assert_eq!(triplet.peer_pk, "failible_peer_id");
assert_eq!(triplet.service_id, "failiable_call_service");
assert_eq!(triplet.function_name, "");
assert_eq!(de_tetraplets[0][0].json_path, "");
Some(IValue::Record(
NEVec::new(vec![IValue::S32(0), IValue::String(tetraplets.clone())]).unwrap(),
))
})
}
#[test]
fn last_error_tetraplets() {
let set_variable_peer_id = "set_variable";
let mut set_variable_vm = create_aqua_vm(unit_call_service(), set_variable_peer_id);
let faillible_peer_id = "failible_peer_id";
let mut faillible_vm = create_aqua_vm(fallible_call_service("falliable_call_service"), faillible_peer_id);
let local_peer_id = "local_peer_id";
let mut local_vm = create_aqua_vm(create_check_service_closure(), local_peer_id);
let script = format!(
include_str!("scripts/create_service_with_xor.clj"),
set_variable_peer_id, faillible_peer_id, local_peer_id
);
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
let res = call_vm!(faillible_vm, "asd", script.clone(), "", res.data);
// assert is done on the 'create_check_service_closure' call service closure
let _ = call_vm!(local_vm, "asd", script, "", res.data);
}

View File

@ -0,0 +1,25 @@
(seq
(seq
(seq
(call "{0}" ("add_module" "") ["module_bytes"] module_bytes)
(call "{0}" ("add_module" "") ["module_config"] module_config)
)
(call "{0}" ("add_module" "") ["blueprint"] blueprint)
)
(xor
(seq
(call "{0}" ("dist" "add_module") [module_bytes module_config] module)
(seq
(call "{0}" ("dist" "add_blueprint") [blueprint] blueprint_id)
(seq
(call "{0}" ("srv" "create") [blueprint_id] service_id)
(call "{1}" ("failiable_call_service" "") [service_id] client_result)
)
)
)
(seq
(call "{1}" ("op" "identity") ["XOR: create_greeting_service failed"] fail[])
(call "{2}" ("return" "") [%last_error%])
)
)
)

View File

@ -11,10 +11,11 @@ dependencies = [
[[package]]
name = "air-parser"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"codespan",
"codespan-reporting",
"itertools 0.10.0",
"lalrpop",
"lalrpop-util",
"regex",
@ -412,6 +413,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
@ -442,7 +452,7 @@ dependencies = [
"diff",
"docopt",
"ena",
"itertools",
"itertools 0.9.0",
"lalrpop-util",
"petgraph",
"regex",
@ -673,7 +683,7 @@ dependencies = [
[[package]]
name = "stepper-lib"
version = "0.4.1"
version = "0.5.0"
dependencies = [
"air-parser",
"boolinator",

View File

@ -11,10 +11,11 @@ dependencies = [
[[package]]
name = "air-parser"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"codespan",
"codespan-reporting",
"itertools 0.10.0",
"lalrpop",
"lalrpop-util",
"regex",
@ -404,6 +405,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
@ -434,7 +444,7 @@ dependencies = [
"diff",
"docopt",
"ena",
"itertools",
"itertools 0.9.0",
"lalrpop-util",
"petgraph",
"regex",
@ -673,7 +683,7 @@ dependencies = [
[[package]]
name = "stepper-lib"
version = "0.4.1"
version = "0.5.0"
dependencies = [
"air-parser",
"boolinator",