Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,3 @@
exports_files([
"logging.ini",
])

View File

@@ -0,0 +1,5 @@
filegroup(
name = "errors",
srcs = glob(["*.yaml"]),
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,10 @@
description: Example errors
errors:
- name: ExampleErrorA
description: Example error A
- name: ExampleErrorB
description: Example error B
- name: ExampleErrorC
description: Example error C
- name: ExampleErrorD
description: Example error D

View File

@@ -0,0 +1,10 @@
description: More errors.
errors:
- name: ExampleErrorA
description: >-
The same error as in example_errors.yaml. The error names are placed in a
namespace
- name: MoreError
description: additinal error
- name: snake_case_error
description: We will correct snake cases

View File

@@ -0,0 +1,2 @@
description: No errors.
errors: []

View File

@@ -0,0 +1,5 @@
filegroup(
name = "interfaces",
srcs = glob(["*.yaml"]),
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1 @@
description: An empty interface.

View File

@@ -0,0 +1,5 @@
description: >-
We make sure that we allow duplicate errors
errors:
- reference: /errors/example_errors#/ExampleErrorA
- reference: /errors/example_errors

View File

@@ -0,0 +1,5 @@
description: >-
We make sure that we respect multiple error definitions
errors:
- reference: /errors/example_errors
- reference: /errors/more_errors

View File

@@ -0,0 +1,4 @@
description: >-
We make sure that we can parse the error files which don't define any errors
errors:
- reference: /errors/no_errors

View File

@@ -0,0 +1,5 @@
description: >-
We make sure that we respect a subset of errors
errors:
- reference: /errors/example_errors#/ExampleErrorA
- reference: /errors/example_errors#/ExampleErrorB

View File

@@ -0,0 +1,20 @@
description: >-
This interface defines an example interface that uses multiple framework
features
cmds:
uses_something:
description: This command checks if something is stored under a given key
arguments:
key:
description: Key to check the existence for
type: string
pattern: ^[A-Za-z0-9_.]+$
result:
description: Returns 'True' if something was stored for this key
type: boolean
vars:
max_current:
description: Provides maximum current of this supply in ampere
type: number
errors:
- reference: /errors/example_errors

View File

@@ -0,0 +1,24 @@
# for documentation on this file format see:
# https://www.boost.org/doc/libs/1_54_0/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.setup.filter_formatter
[Core]
DisableLogging=false
# To get debug logs of only one module, add the "%Process% contains" filter, e.g.:
#
# "(%Process% contains OCPP201 and %Severity% >= DEBG)"
#
# whereas "OCPP201" is the value of the field `active_modules.NAME.module` in the respective /config/config-*.yaml.
Filter="%Severity% >= INFO"
[Sinks.Console]
Destination=Console
# Filter="%Target% contains \"MySink1\""
Format="%TimeStamp% [%Severity%] \033[1;32m%Process%\033[0m \033[1;36m%function%\033[0m \033[1;30m%file%:\033[0m\033[1;32m%line%\033[0m: %Message%"
Asynchronous=false
AutoFlush=true
SeverityStringColorDebug="\033[1;30m"
SeverityStringColorInfo="\033[1;37m"
SeverityStringColorWarning="\033[1;33m"
SeverityStringColorError="\033[1;31m"
SeverityStringColorCritical="\033[1;35m"

View File

@@ -0,0 +1 @@
exports_files(["smoke_test.sh"])

View File

@@ -0,0 +1,96 @@
load("@rules_python//python:defs.bzl", "py_test")
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//applications/utils:requirements.bzl", "requirement")
load("//lib/everest/framework/bazel:everest_env.bzl", "everest_env", "everest_test")
load("//lib/everest/framework/bazel:modules_def.bzl", "rs_everest_module")
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_PYTHON_INCOMPATIBLE")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsAsyncBinary",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
"@everest_framework_crate_index//:tokio",
],
)
rs_everest_module(
name = "RsAsync",
binary = ":RsAsyncBinary",
manifest = "manifest.yaml",
)
everest_env(
name = "config_env",
config_file = "config.yaml",
modules = [
":RsAsync",
],
)
sh_test(
name = "integration_test",
srcs = ["//lib/everest/framework/everestrs/tests/modules:smoke_test.sh"],
data = [":config_env"],
tags = ["exclusive"],
)
# Integration tests for RsAsync. Will launch EVerest with a ProbeModule and
# validate calls to our trait mocks.
rust_test(
name = "RsAsyncMockedTestBinary",
srcs = ["rs_tests/tests.rs"],
compile_data = [
"config.yaml",
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
crate_features = [
"mockall",
"trait",
],
data = ["config_env"],
edition = "2021",
proc_macro_deps = [
"//lib/everest/framework/everestrs/everestrs-derive",
"@everest_framework_crate_index//:mockall_double",
],
rustc_env = {
"CARGO_MANIFEST_DIR": "lib/everest/framework/everestrs/tests/modules/RsAsync",
"EVEREST_CORE_ROOT": "lib/everest/framework/everestrs/tests",
},
tags = ["exclusive"],
deps = [
"//lib/everest/framework/everestrs/everestrs",
"@everest_framework_crate_index//:mockall",
"@everest_framework_crate_index//:tokio",
],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,14 @@
# Test config for regression tests for the framework.
active_modules:
example_0:
module: RsAsync
connections:
receiver:
- module_id: example_1
implementation_id: sender
example_1:
module: RsAsync
connections:
receiver:
- module_id: example_0
implementation_id: sender

View File

@@ -0,0 +1,14 @@
description: Simple Example
provides:
sender:
interface: example
description: An example interface.
requires:
receiver:
interface: example
ignore:
errors: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors

View File

@@ -0,0 +1,60 @@
#![allow(non_snake_case)]
use std::sync::Arc;
use tokio::sync::oneshot;
#[tokio::test]
#[everestrs::test(config = "config.yaml", module = "example_1", harness = true)]
async fn test_tokio_everest(module: &Module) {
use super::*;
use generated::*;
let mock_service = Arc::new(MockExampleServiceSubscriber::new());
let (tx, rx) = oneshot::channel();
let mut mock_client = MockExampleClientSubscriber::new();
mock_client
.expect_on_max_current()
.withf(|_, value| *value == 123.0)
.times(1)
.return_once(move |_, _| {
tx.send(()).unwrap();
});
let mock_client = Arc::new(mock_client);
let mut mock_on_ready = MockOnReadySubscriber::new();
mock_on_ready.expect_on_ready().times(1).return_once(|_| ());
let _pub = module.start(Arc::new(mock_on_ready), mock_service, mock_client);
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.await.expect("Timed out waiting for on_max_current");
}
#[everestrs::test(config = "config.yaml", module = "example_1", harness = true)]
#[tokio::test]
async fn test_everest_tokio(module: &Module) {
use super::*;
use generated::*;
let mock_service = Arc::new(MockExampleServiceSubscriber::new());
let (tx, rx) = oneshot::channel();
let mut mock_client = MockExampleClientSubscriber::new();
mock_client
.expect_on_max_current()
.withf(|_, value| *value == 123.0)
.times(1)
.return_once(move |_, _| {
tx.send(()).unwrap();
});
let mock_client = Arc::new(mock_client);
let mut mock_on_ready = MockOnReadySubscriber::new();
mock_on_ready.expect_on_ready().times(1).return_once(|_| ());
let _pub = module.start(Arc::new(mock_on_ready), mock_service, mock_client);
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.await.expect("Timed out waiting for on_max_current");
}

View File

@@ -0,0 +1,77 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use generated::{
Context, ExampleClientSubscriber, ExampleServiceSubscriber, Module, ModulePublisher,
OnReadySubscriber,
};
use std::sync::{Arc, Mutex};
// Just to use async.
use tokio::sync::oneshot;
use tokio::time::sleep;
pub struct OneClass {
tx: Mutex<Option<oneshot::Sender<String>>>,
}
impl ExampleServiceSubscriber for OneClass {
fn uses_something(&self, _context: &Context, key: String) -> ::everestrs::Result<bool> {
log::info!("Received {key}");
let tx = self.tx.lock().unwrap().take();
if let Some(tx) = tx {
tx.send(key).unwrap();
}
Ok(true)
}
}
impl ExampleClientSubscriber for OneClass {
fn on_max_current(&self, context: &Context, value: f64) {
log::info!("Received {value}");
context
.publisher
.receiver
.uses_something(format!("{value}"))
.unwrap();
}
}
impl OnReadySubscriber for OneClass {
fn on_ready(&self, _publishers: &ModulePublisher) {
log::info!("Ready");
}
}
/// Example how to use async with Everest. Everything in EVerest (all traits)
/// remain strictly sync because of the underlying c++ runtime. However, you can
/// combine your async code with sync EVerest.
///
/// You can combine the `everestrs::main` macro with `tokio::main` macro. The
/// ordering does not really matter, so for non-main function (functions which
/// can receive input args), you can also write
/// ```ignore
/// #[tokio::main]
/// #[everestrs::main]
/// async fn my_fun(module: &Module) {}
/// ```
#[everestrs::main]
#[tokio::main]
async fn main(module: &Module) {
let config = module.get_config();
log::info!("Received the config {config:?}");
let (tx, rx) = oneshot::channel();
let one_class = Arc::new(OneClass {
tx: Mutex::new(Some(tx)),
});
let publishers = module.start(one_class.clone(), one_class.clone(), one_class.clone());
publishers.sender.max_current(123.).unwrap();
// Simulate some async steps...
let result = rx.await.unwrap();
log::info!("Done {result}");
loop {
sleep(std::time::Duration::from_secs(1)).await;
}
}

View File

@@ -0,0 +1,71 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//lib/everest/framework/bazel:everest_env.bzl", "everest_env", "everest_test")
load("//lib/everest/framework/bazel:modules_def.bzl", "rs_everest_module")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsCmdErrorsBinary",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)
rust_test(
name = "RsCmdErrorsTest",
srcs = [],
crate = ":RsCmdErrorsBinary",
crate_features = [
"mockall",
"mockall_double",
],
edition = "2021",
proc_macro_deps = ["@everest_framework_crate_index//:mockall_double"],
deps = ["@everest_framework_crate_index//:mockall"],
)
rs_everest_module(
name = "RsCmdErrors",
binary = ":RsCmdErrorsBinary",
manifest = "manifest.yaml",
)
everest_env(
name = "integration_env",
config_file = "config.yaml",
modules = [
":RsCmdErrors",
],
)
sh_test(
name = "integration_test",
srcs = ["//lib/everest/framework/everestrs/tests/modules:smoke_test.sh"],
data = [":integration_env"],
tags = ["exclusive"],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,16 @@
settings:
# This is the setting we want to test.
forward_exceptions: true
active_modules:
example_0:
module: RsCmdErrors
connections:
other:
- module_id: example_1
implementation_id: example
example_1:
module: RsCmdErrors
connections:
other:
- module_id: example_0
implementation_id: example

View File

@@ -0,0 +1,18 @@
description: Tests if we can raise errors from commands
provides:
example:
interface: example
description: An example interface.
requires:
other:
interface: example
# We don't care about everything which is non-cmd.
ignore:
vars:
- max_current
errors: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors
enable_external_mqtt: false

View File

@@ -0,0 +1,90 @@
//! Integration test for the "cmd-errors" handling.
//!
//! The Rust binding can receive/return errors from command calls. The
//! exceptions need the `forward_exceptions` setting. Then errors from the
//! server should propagate to the client.
//!
//! Below we test all possible errors supported by EVerest. In user code it
//! only `HandlerException` typically makes sense.
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use generated::{
Context, ExampleClientSubscriber, ExampleServiceSubscriber, Module, ModulePublisher,
OnReadySubscriber,
};
use std::sync::{Arc, OnceLock};
use std::{thread, time};
pub struct OneClass {
publisher: OnceLock<ModulePublisher>,
}
impl ExampleServiceSubscriber for OneClass {
fn uses_something(&self, _context: &Context, key: String) -> ::everestrs::Result<bool> {
match key.as_str() {
"MessageParsingError" => Err(::everestrs::Error::MessageParsingError(
"this message?".to_string(),
)),
"SchemaValidationError" => Err(::everestrs::Error::SchemaValidationError(
"not my schema".to_string(),
)),
"HandlerException" => Err(::everestrs::Error::HandlerException(
"my handler".to_string(),
)),
"CmdTimeout" => Err(::everestrs::Error::CmdTimeout("no time".to_string())),
"Shutdown" => Err(::everestrs::Error::Shutdown("dead".to_string())),
"NotReady" => Err(::everestrs::Error::NotReady("too soon".to_string())),
_ => Ok(true),
}
}
}
// The compilation test is that we don't generate the method interfaces for
// the ignored methods.
impl ExampleClientSubscriber for OneClass {}
impl OnReadySubscriber for OneClass {
fn on_ready(&self, publishers: &ModulePublisher) {
let _ = self.publisher.set(publishers.clone());
}
}
#[everestrs::main]
fn main(module: &Module) {
let one_class = Arc::new(OneClass {
publisher: OnceLock::new(),
});
let _publishers = module.start(one_class.clone(), one_class.clone(), one_class.clone());
log::info!("Module initialized");
let publisher = one_class.publisher.wait();
for (key, _expected) in [
(
"HandlerException",
Err(::everestrs::Error::HandlerException(String::new())),
),
("foo", Ok(true)),
(
"SchemaValidationError",
Err(::everestrs::Error::SchemaValidationError(String::new())),
),
(
"MessageParsingError",
Err(::everestrs::Error::MessageParsingError(String::new())),
),
(
"CmdTimeout",
Err(::everestrs::Error::CmdTimeout(String::new())),
),
("Shutdown", Err(::everestrs::Error::Shutdown(String::new()))),
("NotReady", Err(::everestrs::Error::NotReady(String::new()))),
] {
let res = publisher.other.uses_something(key.to_string());
assert!(matches!(res, _expected));
}
loop {
let dt = time::Duration::from_millis(250);
thread::sleep(dt);
}
}

View File

@@ -0,0 +1,66 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//lib/everest/framework/bazel:everest_env.bzl", "everest_env")
load("//lib/everest/framework/bazel:modules_def.bzl", "rs_everest_module")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsErrors",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)
rust_test(
name = "RsErrorsTest",
srcs = [],
crate = ":RsErrors",
edition = "2021",
deps = ["@everest_framework_crate_index//:serde_yaml"],
)
rs_everest_module(
name = "RsErrorsModule",
binary = ":RsErrors",
manifest = "manifest.yaml",
)
everest_env(
name = "integration_env",
config_file = "config.yaml",
modules = [
":RsErrorsModule",
],
)
sh_test(
name = "integration_test",
srcs = ["//lib/everest/framework/everestrs/tests/modules:smoke_test.sh"],
data = [":integration_env"],
tags = ["exclusive"],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,20 @@
# Test config for regression tests for the framework.
active_modules:
errors_0:
module: RsErrorsModule
connections:
errors_multiple:
- module_id: errors_1
implementation_id: multiple
mapping:
module:
evse: 1
errors_1:
module: RsErrorsModule
connections:
errors_multiple:
- module_id: errors_0
implementation_id: multiple
mapping:
module:
evse: 2

View File

@@ -0,0 +1,13 @@
description: Tests for the Errors
requires:
errors_multiple:
interface: errors_multiple
provides:
multiple:
interface: errors_multiple
description: Multiple errors
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors
enable_external_mqtt: false

View File

@@ -0,0 +1,119 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use crate::generated::Context;
use crate::generated::ErrorsMultipleClientSubscriber;
use crate::generated::Module;
use crate::generated::{ModulePublisher, OnReadySubscriber};
use everestrs::{ErrorSeverity, ErrorType};
use generated::errors::errors_multiple::{Error as ExampleError, ExampleErrorsError};
use std::collections::HashSet;
use std::sync::{Arc, Condvar, Mutex};
const MESSAGE: &str = "a message";
const DESCRIPTION: &str = "a description";
const SEVERITY: ErrorSeverity = ErrorSeverity::Low;
struct ErrorCommunacator {
errors_raised: Mutex<HashSet<ExampleErrorsError>>,
errors_cleared: Mutex<HashSet<ExampleErrorsError>>,
errors_cleared_cv: Condvar,
}
impl Eq for ExampleErrorsError {}
impl std::hash::Hash for ExampleErrorsError {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
}
}
impl OnReadySubscriber for ErrorCommunacator {
fn on_ready(&self, publishers: &ModulePublisher) {
let error_a = ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorA);
let error_b = ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorB);
let error_c = ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorC);
publishers.multiple.raise_error(error_a.clone().into());
publishers.multiple.raise_error(error_b.into());
// Raise an error also with description and severity.
let error_c = ErrorType {
error_type: error_c,
description: DESCRIPTION.to_owned(),
message: MESSAGE.to_owned(),
severity: SEVERITY,
};
publishers.multiple.raise_error(error_c);
publishers.multiple.clear_error(error_a);
publishers.multiple.clear_all_errors();
}
}
impl ErrorsMultipleClientSubscriber for ErrorCommunacator {
fn on_error_raised(&self, _context: &Context, error: ErrorType<ExampleError>) {
let mut raised_set = self.errors_raised.lock().unwrap();
log::info!("Error raised {:?}", error.error_type);
if let ExampleError::ExampleErrors(inner) = &error.error_type {
raised_set.insert(inner.clone());
}
// Check the handling for custom message, description and severity.
if let ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorC) = error.error_type {
assert_eq!(&error.description, DESCRIPTION);
assert_eq!(&error.message, MESSAGE);
assert_eq!(error.severity, SEVERITY);
}
}
fn on_error_cleared(&self, _context: &Context, error: ErrorType<ExampleError>) {
let mut cleared_set = self.errors_cleared.lock().unwrap();
log::info!("Error cleared {:?}", error.error_type);
if let ExampleError::ExampleErrors(inner) = error.error_type {
cleared_set.insert(inner.clone());
}
// The integration test links this module to another version of itself
// so the magic 3 here must match the number of calls to raise_error
// (and thus also clear_error through the clear_all_errors call)
// in on_ready
if cleared_set.len() == 3 {
self.errors_cleared_cv.notify_one();
}
}
}
impl crate::generated::ErrorsMultipleServiceSubscriber for ErrorCommunacator {}
#[everestrs::main]
fn main(module: &Module) {
let one_class = Arc::new(ErrorCommunacator {
errors_raised: Mutex::new(HashSet::new()),
errors_cleared: Mutex::new(HashSet::new()),
errors_cleared_cv: Condvar::new(),
});
let _publishers = module.start(one_class.clone(), one_class.clone(), one_class.clone());
let mut tests_passed = false;
// This mutex goes into the condvar, but it is dummy data as we know that the
// notify_once will fire after this
let mutex = Mutex::new(true);
loop {
let mutex_inner = mutex.lock().unwrap();
let res = one_class
.errors_cleared_cv
.wait_timeout(mutex_inner, std::time::Duration::from_secs(2))
.unwrap();
if res.1.timed_out() & !tests_passed {
panic!("Timeout hit");
} else {
let raised_set = one_class.errors_raised.lock().unwrap();
let cleared_set = one_class.errors_cleared.lock().unwrap();
log::info!("Raised Errors: {:?}", raised_set);
log::info!("Cleared Errors: {:?}", cleared_set);
assert_eq!(*raised_set, *cleared_set);
tests_passed = true;
}
}
}

View File

@@ -0,0 +1,42 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsErrorsCompilation",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)
rust_test(
name = "RsErrorsCompilationTest",
srcs = [],
crate = ":RsErrorsCompilation",
edition = "2021",
deps = ["@everest_framework_crate_index//:serde_yaml"],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,25 @@
description: Tests for the Errors
requires:
errors_multiple:
interface: errors_multiple
provides:
duplicate:
interface: errors_duplicate
description: Duplicated errors
multiple:
interface: errors_multiple
description: Multiple errors
selected:
interface: errors_selected
description: Selected errors
none:
interface: errors_none
description: No errors defined
empty:
interface: empty
description: An empty interface
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors
enable_external_mqtt: false

View File

@@ -0,0 +1,86 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
fn main() {}
#[cfg(test)]
mod tests {
/// In this test we check that we can deserialize all four errors.
#[test]
fn test_duplicate() {
use crate::generated::errors::errors_duplicate::Error;
use crate::generated::errors::errors_duplicate::ExampleErrorsError;
for (error_str, error_enum) in [
(
"example_errors/ExampleErrorA",
ExampleErrorsError::ExampleErrorA,
),
(
"example_errors/ExampleErrorB",
ExampleErrorsError::ExampleErrorB,
),
(
"example_errors/ExampleErrorC",
ExampleErrorsError::ExampleErrorC,
),
(
"example_errors/ExampleErrorD",
ExampleErrorsError::ExampleErrorD,
),
] {
assert_eq!(
serde_yaml::from_str::<Error>(error_str).unwrap(),
Error::ExampleErrors(error_enum)
);
}
}
/// In this test we check that we can only deserialize the selected two
/// errors.
#[test]
fn test_selected() {
use crate::generated::errors::errors_selected::Error;
use crate::generated::errors::errors_selected::ExampleErrorsError;
for (error_str, error_enum) in [
(
"example_errors/ExampleErrorA",
ExampleErrorsError::ExampleErrorA,
),
(
"example_errors/ExampleErrorB",
ExampleErrorsError::ExampleErrorB,
),
] {
assert_eq!(
serde_yaml::from_str::<Error>(error_str).unwrap(),
Error::ExampleErrors(error_enum)
);
}
for error_str in [
"example_errors/ExampleErrorC",
"example_errors/ExampleErrorD",
] {
assert!(serde_yaml::from_str::<Error>(error_str).is_err());
}
}
/// This test should just compile. The deserialization is tested above.
#[test]
fn test_multiple() {
{
use crate::generated::errors::errors_multiple::ExampleErrorsError;
let _ = ExampleErrorsError::ExampleErrorA;
let _ = ExampleErrorsError::ExampleErrorB;
let _ = ExampleErrorsError::ExampleErrorC;
let _ = ExampleErrorsError::ExampleErrorD;
}
{
use crate::generated::errors::errors_multiple::MoreErrorsError;
let _ = MoreErrorsError::ExampleErrorA;
let _ = MoreErrorsError::MoreError;
let _ = MoreErrorsError::SnakeCaseError;
}
}
}

View File

@@ -0,0 +1,161 @@
load("@rules_python//python:defs.bzl", "py_test")
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//applications/utils:requirements.bzl", "requirement")
load("//lib/everest/framework/bazel:everest_env.bzl", "everest_env", "everest_test")
load("//lib/everest/framework/bazel:modules_def.bzl", "rs_everest_module")
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_PYTHON_INCOMPATIBLE")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsExampleBinary",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)
rust_test(
name = "RsExampleTest",
srcs = [],
crate = ":RsExampleBinary",
crate_features = [
"mockall",
"mockall_double",
],
edition = "2021",
proc_macro_deps = ["@everest_framework_crate_index//:mockall_double"],
deps = ["@everest_framework_crate_index//:mockall"],
)
rs_everest_module(
name = "RsExample",
binary = ":RsExampleBinary",
manifest = "manifest.yaml",
)
everest_env(
name = "config_env",
config_file = "config.yaml",
modules = [
":RsExample",
],
)
sh_test(
name = "integration_test",
srcs = ["//lib/everest/framework/everestrs/tests/modules:smoke_test.sh"],
data = [":config_env"],
tags = ["exclusive"],
)
everest_env(
name = "config_probe_env",
config_file = "config_probe.yaml",
modules = [
":RsExample",
],
)
# Integration tests for RsExample using Python's everest.testing framework.
# Uses ProbeModule to verify RsExample publishes variables and handles commands.
py_test(
name = "py_mocked_test",
srcs = glob(["py_tests/**/*.py"]),
data = ["config_probe_env"],
legacy_create_init = False,
main = "py_tests/mocked_test.py",
tags = ["exclusive"],
target_compatible_with = CROSS_PYTHON_INCOMPATIBLE,
deps = [
"//applications/utils/everest-testing",
"//lib/everest/framework/everestpy/src:framework",
requirement("pytest"),
requirement("pytest-asyncio"),
],
)
# Integration tests for RsExample. Will launch EVerest with a ProbeModule and
# verify that we can launch our module together with the ProbeModule.
rust_test(
name = "RsExampleHarnessTestBinary",
srcs = ["rs_tests/harness_test.rs"],
compile_data = [
"config_probe.yaml",
"config_multiple_connections.yaml",
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
data = ["config_probe_env"],
edition = "2021",
proc_macro_deps = [
"//lib/everest/framework/everestrs/everestrs-derive",
],
rustc_env = {
"CARGO_MANIFEST_DIR": "lib/everest/framework/everestrs/tests/modules/RsExample",
"EVEREST_CORE_ROOT": "lib/everest/framework/everestrs/tests",
},
tags = ["exclusive"],
deps = [
"//lib/everest/framework/everestrs/everestrs",
],
)
# Integration tests for RsExample. Will launch EVerest with a ProbeModule and
# validate calls to our trait mocks.
rust_test(
name = "RsExampleMockedTestBinary",
srcs = ["rs_tests/mocked_test.rs"],
compile_data = [
"config_probe.yaml",
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
crate_features = [
"mockall",
"trait",
],
data = ["config_probe_env"],
edition = "2021",
proc_macro_deps = [
"//lib/everest/framework/everestrs/everestrs-derive",
"@everest_framework_crate_index//:mockall_double",
],
rustc_env = {
"CARGO_MANIFEST_DIR": "lib/everest/framework/everestrs/tests/modules/RsExample",
"EVEREST_CORE_ROOT": "lib/everest/framework/everestrs/tests",
},
tags = ["exclusive"],
deps = [
"//lib/everest/framework/everestrs/everestrs",
"@everest_framework_crate_index//:mockall",
],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,14 @@
# Test config for regression tests for the framework.
active_modules:
example_0:
module: RsExample
connections:
a_friend:
- module_id: example_1
implementation_id: foobar
example_1:
module: RsExample
connections:
a_friend:
- module_id: example_0
implementation_id: foobar

View File

@@ -0,0 +1,21 @@
active_modules:
example_0:
module: RsExample
connections:
a_friend:
- module_id: probe
implementation_id: foobar
example_1:
module: RsExample
connections:
a_friend:
- module_id: probe
implementation_id: foobar
probe:
module: ProbeModule
connections:
a_friend:
- module_id: example_0
implementation_id: foobar
- module_id: example_1
implementation_id: foobar

View File

@@ -0,0 +1,14 @@
# Test config for regression tests for the framework.
active_modules:
example_0:
module: RsExample
connections:
a_friend:
- module_id: example_1
implementation_id: foobar
example_1:
module: ProbeModule
connections:
a_friend:
- module_id: example_0
implementation_id: foobar

View File

@@ -0,0 +1,30 @@
description: Simple Example
config:
some_string_config:
description: A module level string config.
type: string
default: Hello world
some_number_config:
description: A module level number config.
type: number
default: 42
provides:
foobar:
interface: example
description: An example interface.
config:
some_bool_config:
description: An interface level bool config
type: boolean
default: true
some_integer_config:
description: An interface level integer config.
type: integer
default: 1234
requires:
a_friend:
interface: example
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright Pionix GmbH and Contributors to EVerest
def pytest_addoption(parser):
parser.addoption(
"--everest-prefix",
action="store",
default=".",
help="everest prefix path; default = '.' (for bazel runfiles)",
)

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright Pionix GmbH and Contributors to EVerest
"""
Smoke test for RsExample using the Python testing framework.
Launches EVerest with RsExample (example_0) and a ProbeModule (example_1),
then verifies that RsExample publishes max_current(123.0) on ready and
responds to the uses_something command.
"""
import asyncio
import sys
from unittest.mock import Mock
import pytest
from everest.testing.core_utils.common import Requirement
from everest.testing.core_utils.fixtures import *
from everest.testing.core_utils.everest_core import EverestCore
from everest.testing.core_utils.probe_module import ProbeModule
async def wait_for_mock(mock, timeout=10):
"""Poll until mock has been called or timeout."""
for _ in range(int(timeout * 10)):
if mock.called:
return
await asyncio.sleep(0.1)
raise TimeoutError(f"Mock not called within {timeout}s")
@pytest.mark.asyncio
@pytest.mark.probe_module(
connections={"a_friend": [Requirement("example_0", "foobar")]},
module_id="example_1",
)
@pytest.mark.everest_core_config("config_probe.yaml")
async def test_rs_example_publishes_max_current(everest_core: EverestCore):
"""Verify that RsExample publishes max_current(123.0) on ready."""
everest_core.start()
probe = ProbeModule(everest_core.get_runtime_session(), module_id="example_1")
max_current_mock = Mock()
probe.subscribe_variable("a_friend", "max_current", max_current_mock)
probe.implement_command("foobar", "uses_something", lambda args: True)
probe.start()
await probe.wait_to_be_ready(timeout=10)
await wait_for_mock(max_current_mock, timeout=10)
value = max_current_mock.call_args[0][0]
assert value == 123.0, f"Expected max_current=123.0, got {value}"
@pytest.mark.asyncio
@pytest.mark.probe_module(
connections={"a_friend": [Requirement("example_0", "foobar")]},
module_id="example_1",
)
@pytest.mark.everest_core_config("config_probe.yaml")
async def test_rs_example_uses_something(everest_core: EverestCore):
"""Verify that we can call uses_something on RsExample."""
everest_core.start()
probe = ProbeModule(everest_core.get_runtime_session(), module_id="example_1")
probe.implement_command("foobar", "uses_something", lambda args: True)
probe.start()
await probe.wait_to_be_ready(timeout=10)
result = await probe.call_command("a_friend", "uses_something", {"key": "hello"})
# The C++ binding may return None for boolean results; verify at least no exception.
assert result is None or result == True, f"Unexpected result: {result!r}"
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v"] + sys.argv[1:]))

View File

@@ -0,0 +1,148 @@
#![allow(non_snake_case)]
#[everestrs::test(config = "config_probe.yaml", module = "example_1", harness = true)]
fn test_harness_generates_counterpart(module: &Module) {
use generated::*;
use std::sync::mpsc::{channel, Sender};
use std::sync::Arc;
let (tx, rx) = channel();
struct Dummy(Sender<()>);
impl OnReadySubscriber for Dummy {
fn on_ready(&self, _pub_impl: &ModulePublisher) {}
}
impl ExampleServiceSubscriber for Dummy {
fn uses_something(&self, _context: &Context, _key: String) -> everestrs::Result<bool> {
Ok(false)
}
}
impl ExampleClientSubscriber for Dummy {
fn on_max_current(&self, _context: &Context, value: f64) {
assert_eq!(value, 123.);
self.0.send(()).unwrap();
}
fn on_error_raised(
&self,
_context: &Context,
_error: everestrs::ErrorType<errors::example::Error>,
) {
}
fn on_error_cleared(
&self,
_context: &Context,
_error: everestrs::ErrorType<errors::example::Error>,
) {
}
}
let dummy = Arc::new(Dummy(tx));
let _pub = module.start(dummy.clone(), dummy.clone(), dummy.clone());
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("Timed out waiting for on_max_current");
}
#[everestrs::harness(config = "config_probe.yaml", module = "example_1")]
mod some_module {
use generated::*;
use std::sync::mpsc::{channel, Sender};
use std::sync::Arc;
struct Dummy(Sender<()>, f64);
impl OnReadySubscriber for Dummy {
fn on_ready(&self, _pub_impl: &ModulePublisher) {}
}
impl ExampleServiceSubscriber for Dummy {
fn uses_something(&self, _context: &Context, _key: String) -> everestrs::Result<bool> {
Ok(false)
}
}
impl ExampleClientSubscriber for Dummy {
fn on_max_current(&self, _context: &Context, value: f64) {
assert_eq!(value, self.1);
self.0.send(()).unwrap();
}
fn on_error_raised(
&self,
_context: &Context,
_error: everestrs::ErrorType<errors::example::Error>,
) {
}
fn on_error_cleared(
&self,
_context: &Context,
_error: everestrs::ErrorType<errors::example::Error>,
) {
}
}
#[everestrs::test(config = "config_probe.yaml", module = "example_1")]
fn test_harness_in_module(module: &Module) {
let (tx, rx) = channel();
let dummy = Arc::new(Dummy(tx, 123.));
let _pub = module.start(dummy.clone(), dummy.clone(), dummy.clone());
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("Timed out waiting for on_max_current");
}
#[everestrs::test(config = "config_probe.yaml", module = "example_1")]
#[should_panic]
fn test_harness_with_panic(module: &Module) {
let (tx, rx) = channel();
let dummy = Arc::new(Dummy(tx, 124.));
let _pub = module.start(dummy.clone(), dummy.clone(), dummy.clone());
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("Timed out waiting for on_max_current");
}
}
#[everestrs::harness(config = "config_multiple_connections.yaml", module = "probe")]
mod multiple_connections_compilation {
use generated::*;
use std::sync::Arc;
struct Dummy;
impl OnReadySubscriber for Dummy {
fn on_ready(&self, _pub_impl: &ModulePublisher) {}
}
impl ExampleServiceSubscriber for Dummy {
fn uses_something(&self, _context: &Context, _key: String) -> everestrs::Result<bool> {
Ok(false)
}
}
impl ExampleClientSubscriber for Dummy {
fn on_max_current(&self, _context: &Context, _value: f64) {}
fn on_error_raised(
&self,
_context: &Context,
_error: everestrs::ErrorType<errors::example::Error>,
) {
}
fn on_error_cleared(
&self,
_context: &Context,
_error: everestrs::ErrorType<errors::example::Error>,
) {
}
}
#[everestrs::test(config = "config_probe.yaml", module = "example_1")]
fn test_harness_in_module(module: &Module) {
let dummy = Arc::new(Dummy);
let _pub = module.start(dummy.clone(), dummy.clone(), |_a: usize| dummy.clone());
}
}

View File

@@ -0,0 +1,104 @@
#![allow(non_snake_case)]
#[everestrs::test(config = "config_probe.yaml", module = "example_1", harness = true)]
fn test_mocked_generates_counterpart(module: &Module) {
use generated::*;
use std::sync::Arc;
let mock_service = Arc::new(MockExampleServiceSubscriber::new());
let (tx, rx) = std::sync::mpsc::channel();
let mut mock_client = MockExampleClientSubscriber::new();
mock_client
.expect_on_max_current()
.withf(|_, value| *value == 123.0)
.times(1)
.return_once(move |_, _| {
tx.send(()).unwrap();
});
let mock_client = Arc::new(mock_client);
let mut mock_on_ready = MockOnReadySubscriber::new();
mock_on_ready.expect_on_ready().times(1).return_once(|_| ());
let _pub = module.start(Arc::new(mock_on_ready), mock_service, mock_client);
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("Timed out waiting for on_max_current");
}
#[everestrs::harness(config = "config_probe.yaml", module = "example_1")]
mod some_module {
use generated::*;
use std::sync::Arc;
#[everestrs::test(config = "config_probe.yaml", module = "example_1")]
fn test_mocked_in_module(module: &Module) {
let mock_service = Arc::new(MockExampleServiceSubscriber::new());
let (tx, rx) = std::sync::mpsc::channel();
let mut mock_client = MockExampleClientSubscriber::new();
mock_client
.expect_on_max_current()
.withf(|_, value| *value == 123.0)
.times(1)
.return_once(move |_, _| {
tx.send(()).unwrap();
});
let mock_client = Arc::new(mock_client);
let mut mock_on_ready = MockOnReadySubscriber::new();
mock_on_ready.expect_on_ready().times(1).return_once(|_| ());
let _pub = module.start(Arc::new(mock_on_ready), mock_service, mock_client);
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("Timed out waiting for on_max_current");
}
#[everestrs::test(config = "config_probe.yaml", module = "example_1")]
#[should_panic]
fn test_mocked_with_panic_handler(module: &Module) {
let mock_service = Arc::new(MockExampleServiceSubscriber::new());
let (tx, rx) = std::sync::mpsc::channel();
let mut mock_client = MockExampleClientSubscriber::new();
mock_client
.expect_on_max_current()
.withf(|_, value| *value != 123.0)
.times(1)
.return_once(move |_, _| {
tx.send(()).unwrap();
});
let mock_client = Arc::new(mock_client);
let mut mock_on_ready = MockOnReadySubscriber::new();
mock_on_ready.expect_on_ready().times(1).return_once(|_| ());
let _pub = module.start(Arc::new(mock_on_ready), mock_service, mock_client);
// Wait for RsExample's on_ready to publish max_current(123.0).
rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("Timed out waiting for on_max_current");
}
#[everestrs::test(config = "config_probe.yaml", module = "example_1")]
#[should_panic]
fn test_mocked_with_panic_mocks(module: &Module) {
let mock_service = Arc::new(MockExampleServiceSubscriber::new());
let mut mock_client = MockExampleClientSubscriber::new();
mock_client
.expect_on_max_current()
.times(2..) // No one will call us twice.
.return_const(());
let mock_client = Arc::new(mock_client);
let mut mock_on_ready = MockOnReadySubscriber::new();
mock_on_ready.expect_on_ready().times(1).return_once(|_| ());
let _pub = module.start(Arc::new(mock_on_ready), mock_service, mock_client);
}
}

View File

@@ -0,0 +1,128 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use everestrs::ErrorType;
use generated::errors::example::Error as ExampleError;
use generated::{
Context, ExampleClientSubscriber, ExampleServiceSubscriber, Module,
ModulePublisher, OnReadySubscriber,
};
use std::sync::Arc;
use std::{thread, time};
pub struct OneClass {}
impl ExampleServiceSubscriber for OneClass {
fn uses_something(&self, context: &Context, key: String) -> ::everestrs::Result<bool> {
use crate::generated::errors::example::ExampleErrorsError;
let error = ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorA);
if key.is_empty() {
// Explicit cast
let error: ErrorType<_> = error.into();
context.publisher.foobar.raise_error(error);
} else if &key == "clear_all" {
context.publisher.foobar.clear_all_errors();
} else {
context.publisher.foobar.clear_error(error);
}
Ok(true)
}
}
impl ExampleClientSubscriber for OneClass {
fn on_max_current(&self, _context: &Context, value: f64) {
log::info!("Received {value}");
}
fn on_error_raised(&self, _context: &Context, error: ErrorType<ExampleError>) {
log::warn!("Recieved an error {:?}", error.error_type);
}
fn on_error_cleared(&self, _context: &Context, error: ErrorType<ExampleError>) {
log::info!("Cleared an error {:?} - what a relief", error.error_type);
}
}
impl OnReadySubscriber for OneClass {
fn on_ready(&self, publishers: &ModulePublisher) {
log::info!("Ready");
match publishers.foobar.max_current(123.0) {
Ok(_) => log::info!("Adjusted the max current"),
Err(err) => log::error!("Failed to set the max current: {err:?}"),
}
}
}
#[everestrs::main]
fn main(module: &Module) {
let config = module.get_config();
log::info!("Received the config {config:?}");
let one_class = Arc::new(OneClass {});
let _publishers = module.start(one_class.clone(), one_class.clone(), one_class.clone());
log::info!("Module initialized");
loop {
let dt = time::Duration::from_millis(250);
thread::sleep(dt);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_on_ready() {
let mut everest_mock = ModulePublisher::default();
everest_mock
.foobar
.expect_max_current()
.times(1)
.return_once(|_| Ok(()));
let module = OneClass {};
module.on_ready(&everest_mock);
}
#[test]
fn test_uses_something() {
use mockall::Sequence;
let mut seq = Sequence::new();
let mut everest_mock = ModulePublisher::default();
everest_mock
.foobar
.expect_raise_error()
.times(1)
.in_sequence(&mut seq)
.return_once(|_| ());
everest_mock
.foobar
.expect_clear_error()
.times(1)
.in_sequence(&mut seq)
.return_once(|_| ());
everest_mock
.foobar
.expect_clear_all_errors()
.times(1)
.in_sequence(&mut seq)
.return_once(|| ());
let context = Context {
name: "foo",
publisher: &everest_mock,
index: 0,
};
let module = OneClass {};
for message in [String::new(), "clear".to_owned(), "clear_all".to_owned()] {
let _ = module.uses_something(&context, message);
}
}
}

View File

@@ -0,0 +1,58 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//lib/everest/framework/bazel:everest_env.bzl", "everest_env")
load("//lib/everest/framework/bazel:modules_def.bzl", "rs_everest_module")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsIgnoreBinary",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)
rs_everest_module(
name = "RsIgnore",
binary = ":RsIgnoreBinary",
manifest = "manifest.yaml",
)
everest_env(
name = "integration_env",
config_file = "config.yaml",
modules = [
":RsIgnore",
],
)
sh_test(
name = "integration_test",
srcs = ["//lib/everest/framework/everestrs/tests/modules:smoke_test.sh"],
data = [":integration_env"],
tags = ["exclusive"],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,14 @@
# Test config for regression tests for the framework.
active_modules:
example_0:
module: RsIgnore
connections:
other:
- module_id: example_1
implementation_id: example
example_1:
module: RsIgnore
connections:
other:
- module_id: example_0
implementation_id: example

View File

@@ -0,0 +1,17 @@
description: Simple Example
provides:
example:
interface: example
description: An example interface.
requires:
other:
interface: example
ignore:
vars:
- max_current
errors: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors
enable_external_mqtt: false

View File

@@ -0,0 +1,50 @@
//! Integration test for the "ignore" handling.
//!
//! The Rust binding allow you to ignore variables and errors. The variables
//! are ignored by adding them to the "ignore.vars" list. Errors can only be ignored
//! at bulk, by setting "ignore.errors" to true. Ignored elements are removed
//! from the trait and thus don't have to be implemented.
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use generated::errors::example::{Error as ExampleError, ExampleErrorsError};
use generated::{
Context, ExampleClientSubscriber, ExampleServiceSubscriber, Module, ModulePublisher,
OnReadySubscriber,
};
use std::sync::Arc;
use std::{thread, time};
pub struct OneClass {}
impl ExampleServiceSubscriber for OneClass {
fn uses_something(&self, _context: &Context, _key: String) -> ::everestrs::Result<bool> {
Ok(true)
}
}
// The compilation test is that we don't generate the method interfaces for
// the ignored methods.
impl ExampleClientSubscriber for OneClass {}
impl OnReadySubscriber for OneClass {
fn on_ready(&self, publishers: &ModulePublisher) {
// Call the other module. This calls should be ignored.
publishers.example.max_current(12.3).unwrap();
let error = ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorA);
publishers.example.raise_error(error.clone().into());
publishers.example.clear_error(error);
}
}
#[everestrs::main]
fn main(module: &Module) {
let one_class = Arc::new(OneClass {});
let _publishers = module.start(one_class.clone(), one_class.clone(), one_class.clone());
log::info!("Module initialized");
loop {
let dt = time::Duration::from_millis(250);
thread::sleep(dt);
}
}

View File

@@ -0,0 +1,58 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//lib/everest/framework/bazel:everest_env.bzl", "everest_env")
load("//lib/everest/framework/bazel:modules_def.bzl", "rs_everest_module")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
rust_binary(
name = "RsOnReadyRaceConditionBinary",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)
rs_everest_module(
name = "RsOnReadyRaceCondition",
binary = ":RsOnReadyRaceConditionBinary",
manifest = "manifest.yaml",
)
everest_env(
name = "integration_env",
config_file = "config.yaml",
modules = [
":RsOnReadyRaceCondition",
],
)
sh_test(
name = "integration_test",
srcs = ["//lib/everest/framework/everestrs/tests/modules:smoke_test.sh"],
data = [":integration_env"],
tags = ["exclusive"],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,14 @@
# Test config for regression tests for the framework.
active_modules:
example_0:
module: RsOnReadyRaceCondition
connections:
other:
- module_id: example_1
implementation_id: example
example_1:
module: RsOnReadyRaceCondition
connections:
other:
- module_id: example_0
implementation_id: example

View File

@@ -0,0 +1,13 @@
description: Simple Example
provides:
example:
interface: example
description: An example interface.
requires:
other:
interface: example
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors
enable_external_mqtt: false

View File

@@ -0,0 +1,82 @@
//! Integration test for the "ready_received" handling.
//!
//! We assume that every module shall recieve first `on_ready` before forwarding
//! any other call to the user code. The code below recreates the race condition
//! by making calls to the `other` module from within `on_ready` (and adding a
//! delay in `on_ready`).
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use everestrs::ErrorType;
use generated::errors::example::{Error as ExampleError, ExampleErrorsError};
use generated::{
Context, ExampleClientSubscriber, ExampleServiceSubscriber, Module, ModulePublisher,
OnReadySubscriber,
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{thread, time};
pub struct OneClass {
/// Flag that the on-ready has been called.
on_ready_called: AtomicBool,
}
impl ExampleServiceSubscriber for OneClass {
fn uses_something(&self, _context: &Context, _key: String) -> ::everestrs::Result<bool> {
assert!(self.on_ready_called.load(Ordering::Relaxed));
Ok(true)
}
}
impl ExampleClientSubscriber for OneClass {
fn on_max_current(&self, _context: &Context, _value: f64) {
assert!(self.on_ready_called.load(Ordering::Relaxed));
log::info!("max current");
}
fn on_error_raised(&self, _context: &Context, _error: ErrorType<ExampleError>) {
assert!(self.on_ready_called.load(Ordering::Relaxed));
log::info!("Error raised");
}
fn on_error_cleared(&self, _context: &Context, _error: ErrorType<ExampleError>) {
assert!(self.on_ready_called.load(Ordering::Relaxed));
log::info!("Error cleared");
}
}
impl OnReadySubscriber for OneClass {
fn on_ready(&self, publishers: &ModulePublisher) {
log::info!("Enter Ready");
// Call the other module.
publishers.example.max_current(12.3).unwrap();
let error = ExampleError::ExampleErrors(ExampleErrorsError::ExampleErrorA);
publishers.example.raise_error(error.clone().into());
publishers.example.clear_error(error);
// TODO(ddo) Add here the `uses_something` call once the framework can
// reject too early calls.
// Sleep here to trigger the race condition.
std::thread::sleep(std::time::Duration::from_secs(1));
// Update the flag.
self.on_ready_called.store(true, Ordering::Relaxed);
log::info!("Exit Ready!");
}
}
#[everestrs::main]
fn main(module: &Module) {
let one_class = Arc::new(OneClass {
on_ready_called: AtomicBool::new(false),
});
let _publishers = module.start(one_class.clone(), one_class.clone(), one_class.clone());
log::info!("Module initialized");
loop {
let dt = time::Duration::from_millis(250);
thread::sleep(dt);
}
}

View File

@@ -0,0 +1,35 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
build_script_env = {
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"//lib/everest/framework/everestrs/tests/errors",
"//lib/everest/framework/everestrs/tests/interfaces",
"//lib/everest/framework/everestrs/tests/types",
],
edition = "2021",
deps = [
"//lib/everest/framework/everestrs/everestrs-build",
],
)
# For now just a compilation test
rust_binary(
name = "RsOptionalConnection",
srcs = glob(["src/**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
":build_script",
"//lib/everest/framework/everestrs/everestrs",
"//lib/everest/framework/everestrs/everestrs:everestrs_bridge",
"//lib/everest/framework/everestrs/everestrs:everestrs_sys",
"@everest_framework_crate_index//:log",
],
)

View File

@@ -0,0 +1,13 @@
use everestrs_build::Builder;
pub fn main() {
Builder::new(
"manifest.yaml",
vec![std::env::var("EVEREST_CORE_ROOT").unwrap_or("../../..".to_string())],
)
.generate()
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=manifest.yaml");
}

View File

@@ -0,0 +1,15 @@
description: The tests how to use optinal connections in Rust
provides:
foobar:
interface: example
description: An example interface.
requires:
optional_connection:
interface: example
min_connections: 0
max_connections: 1
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Everest authors
enable_external_mqtt: false

View File

@@ -0,0 +1,58 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
use everestrs::ErrorType;
use generated::errors::example::Error as ExampleError;
use generated::{
Context, ExampleClientSubscriber, ExampleServiceSubscriber, Module, ModulePublisher,
OnReadySubscriber,
};
use std::sync::Arc;
use std::{thread, time};
pub struct OptionalConnection {}
impl ExampleServiceSubscriber for OptionalConnection {
fn uses_something(&self, _context: &Context, key: String) -> ::everestrs::Result<bool> {
log::info!("Received {key}");
Ok(&key == "hello")
}
}
impl ExampleClientSubscriber for OptionalConnection {
fn on_max_current(&self, _context: &Context, value: f64) {
log::info!("Received {value}");
}
fn on_error_raised(&self, _context: &Context, error: ErrorType<ExampleError>) {
log::info!("Recieved an error {:?}", error.error_type);
}
fn on_error_cleared(&self, _context: &Context, error: ErrorType<ExampleError>) {
log::info!("Cleared an error {:?} - what a relief", error.error_type);
}
}
impl OnReadySubscriber for OptionalConnection {
fn on_ready(&self, publishers: &ModulePublisher) {
log::info!("Ready");
if let Some(publisher) = publishers.optional_connection_slots.get(0) {
let res = publisher.uses_something("hello".to_string()).unwrap();
assert!(res);
}
}
}
#[everestrs::main]
fn main(module: &Module) {
let one_class = Arc::new(OptionalConnection {});
let _publishers = module.start(one_class.clone(), one_class.clone(), |_index| {
one_class.clone()
});
log::info!("Module initialized");
loop {
let dt = time::Duration::from_millis(250);
thread::sleep(dt);
}
}

View File

@@ -0,0 +1,18 @@
#!/bin/sh
set -ex
echo "Starting the manager"
bin/manager --prefix . --config etc/everest/config.yaml &
PID_MANAGER=$!
sleep 5
echo "Exit"
if ps -p $PID_MANAGER > /dev/null
then
kill $PID_MANAGER
else
echo "manager died"
exit 1
fi

View File

@@ -0,0 +1,5 @@
filegroup(
name = "types",
srcs = glob(["*.yaml"]),
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,5 @@
description: Example type
types:
Something:
description: Some simple type.
type: string