Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scripting fixes20231027 #171

Merged
merged 10 commits into from
Oct 31, 2023
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,10 @@ Changes:
- Major changes: Javascript scripting!
- Updated config-wasm to parse legacy and scripting yaml files
- New binary pewpew-config-updater will attempt to convert legacy config yamls to the new version. If it can't convert the code it will leave in PLACEHOLDERS and TODO
- Known issues:
- Known issues in the config-updater:
- Expressions in vars will not wrap environment variables in the expected `${e:VAR}`
- vars in `logs` and `provides` will not have the prepended `_v.` before the var name.

Bugs:
- Collect returns an array of strings regardless of input type. Workaround, use scripting to `.map(parseInt)`.
- Declare expressions that create strings will escape out any json/quotes. No workaround currently.
- Vars cannot be decimal point values. Ex `peakLoad: 0.87`. Workaround: `peakLoad: ${x:0.87}`
- global loggers may not be running in try script

Bug fixes:

### v0.5.13
Expand Down
75 changes: 75 additions & 0 deletions examples/declare.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
load_pattern:
- !linear
to: 100%
over: 15s
loggers:
l:
to: !stdout
vars:
var_string: "foo"
var_array: ["foo", "foo", "foo"]
port: "${e:PORT}"
# a var that is an an object as a string.
# Putting this in directly will escape out all the quotes. You must wrap it as ${x:(${v:var_string_json})} to avoid escaping
var_string_json: '{"d":234,"e":"pqr","f":2.34}'
# a var that is a an object as an object
var_object: {"d":890,"e":"xyz","f":7.89}
providers:
provider_range:
!range
provider_list_objects: # List of objects
!list
values:
- a: 123
b: 'abc'
c: 1.23
- a: 456
b: 'def'
c: 4.56
- a: 789
b: 'ghi'
c: 7.89
- a: 101112
b: 'jkl'
c: 10.1112
repeat: true
endpoints:
- method: POST
declare:
# Collects the string into an array
collect_strings: !c
collects:
- take: 3
from: '${v:var_string}'
as: _b
then: ${p:_b}
# Collects the objects into an array
collect_objects: !c
collects:
- take: 2
from: '${p:provider_list_objects}'
as: _c
then: ${p:_c}
declare_string_json: !x '[{"provider_range": ${p:provider_range}, "var_string": "${v:var_string}","provider_list_objects": ${p:provider_list_objects}, "h": "tuv" }]'
url: http://localhost:${v:port}/
body: !str >-
{
"provider_range": ${p:provider_range},
"var_string": "${v:var_string}",
"provider_list_objects": ${p:provider_list_objects},
"collect_objects": ${p:collect_objects},
"collect_strings": ${p:collect_strings},
"var_array": ${v:var_array},
"var_string_json": ${x:(${v:var_string_json})},
"var_object": ${v:var_object},
"declare_string_json": ${p:declare_string_json},
"const_string": "test"
}
peak_load: 1.1hps
headers:
Content-Type: application/json
logs:
l:
select:
status: response.status
body: response.body
2 changes: 1 addition & 1 deletion lib/config/src/configv1/convert_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ fn map_vars(v: PreVar) -> Result<VarValue<False>, ConfigUpdaterError> {
match v {
json::Value::Null => unimplemented!("null var"),
json::Value::Bool(b) => Ok(VarValue::Bool(b)),
json::Value::Number(n) => Ok(VarValue::Num(n.as_i64().unwrap_or_default())),
json::Value::Number(n) => Ok(VarValue::Num(n.as_f64().unwrap_or_default())),
json::Value::String(s) => {
let t = TemplateV1::new(
&s,
Expand Down
10 changes: 10 additions & 0 deletions lib/config/src/configv2/endpoints/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
};
use ether::Either;
use futures::{Stream, StreamExt, TryStreamExt};
use log::debug;
use serde::Deserialize;
use std::{
collections::{BTreeMap, BTreeSet},
Expand Down Expand Up @@ -177,6 +178,11 @@ impl Declare<True> {
Ar: Clone + Send + Unpin + 'static,
E: StdError + Send + Clone + Unpin + 'static + From<EvalExprError>,
{
debug!(
"Declare into_stream declare={:?}, providers={:?}",
self,
providers.keys()
);
// poll_fn stream is not Clone, so an abstraction is made over a clonable stream vs a
// function that returns a non-clonable stream
fn make_stream<S: Clone, F: Fn() -> S2, S2>(e: &Either<S, F>) -> Either<S, S2> {
Expand All @@ -193,6 +199,10 @@ impl Declare<True> {
.map(|Collect { take, from, r#as }| {
let providers = providers.clone();
let stream = {
debug!(
"Declare Collects take={:?}, from={:?} as={}",
take, from, r#as
);
match from.as_static().map(ToOwned::to_owned) {
// collect does not need providers, so just repeat the same value
// as needed
Expand Down
2 changes: 2 additions & 0 deletions lib/config/src/configv2/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub enum LoadTestGenError {
LibLoad(#[from] Arc<io::Error>),
#[error("endpoints are required")]
NoEndpoints(),
#[error("error missing providers: {0:?}")]
MissingProviders(Vec<Arc<str>>),
// Used by the config-wasm when only passing back a V1 error
#[error("error {0}")]
OtherErr(String),
Expand Down
54 changes: 36 additions & 18 deletions lib/config/src/configv2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ pub struct LoadTest<VD: Bool = True, ED: Bool = True> {

pub(crate) type Vars<ED> = BTreeMap<Arc<str>, VarValue<ED>>;

#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
#[derive(serde::Serialize)]
pub(crate) enum VarValue<ED: Bool> {
Map(Vars<ED>),
Num(i64),
Num(f64),
Bool(bool),
Str(Template<String, EnvsOnly, True, ED>),
List(Vec<Self>),
Expand All @@ -72,7 +72,7 @@ impl From<VarValue<True>> for serde_json::Value {
fn from(value: VarValue<True>) -> Self {
match value {
VarValue::Bool(b) => Self::Bool(b),
VarValue::Num(n) => Self::Number(n.into()),
VarValue::Num(n) => Self::Number(serde_json::Number::from_f64(n).unwrap()),
VarValue::Str(mut t) => Self::String(std::mem::take(t.get_mut())),
VarValue::List(l) => l.into_iter().map(Into::into).collect::<Vec<Self>>().into(),
VarValue::Map(m) => Self::Object(
Expand All @@ -95,9 +95,14 @@ impl Display for VarValue<True> {
}
Self::Map(m) => {
write!(f, "{{")?;
let mut multiple_entries = false;
for (k, v) in m.iter() {
if multiple_entries {
// We need to not write a trailing slash, so only add this before the second
write!(f, ",")?;
}
write!(f, "\"{}\": {}", k.escape_default(), v)?;
write!(f, ",")?;
multiple_entries = true;
}
write!(f, "}}")
}
Expand Down Expand Up @@ -166,10 +171,10 @@ impl LoadTest<True, True> {
file_path: Arc<Path>,
env_vars: &BTreeMap<String, String>,
) -> Result<Self, LoadTestGenError> {
use LoadTestGenError::NoEndpoints;
use LoadTestGenError::{MissingProviders, NoEndpoints};
// TODO: Why isn't this causing errors on empty
let mut pre_envs: LoadTest<False, False> = serde_yaml::from_str(yaml)?;
log::debug!("from_yaml pre_envs: {:?}", pre_envs);
log::debug!("LoadTest::from_yaml pre_envs: {:?}", pre_envs);
// init lib js
scripting::set_source(std::mem::take(&mut pre_envs.lib_src))?;

Expand All @@ -179,34 +184,47 @@ impl LoadTest<True, True> {
.iter_mut()
.for_each(|e| e.insert_path(Arc::clone(&file_path)));
let vars = std::mem::take(&mut pre_vars.vars);
let mut lt = pre_vars.insert_vars(&vars)?;
let mut loadtest = pre_vars.insert_vars(&vars)?;

// Check if providers all exists for the templates
let missing = lt
let missing = loadtest
.get_required_providers()
.into_iter()
.filter(|p| !lt.providers.contains_key::<str>(p))
.filter(|p| !loadtest.providers.contains_key::<str>(p))
.collect::<Vec<_>>();
if !missing.is_empty() {
todo!("error on missing providers: {missing:?}");
return Err(MissingProviders(missing));
}

let lp = &lt.load_pattern;
let ep = &mut lt.endpoints;
let headers = &lt.config.client.headers;
let loggers = &loadtest.loggers;
let load_pattern = &loadtest.load_pattern;
let endpoints = &mut loadtest.endpoints;
let headers = &loadtest.config.client.headers;
// Check for no endpoints
if ep.is_empty() {
if endpoints.is_empty() {
return Err(NoEndpoints());
}
ep.iter_mut().enumerate().for_each(|(id, endpoint)| {
endpoint.insert_load_pattern(lp.as_ref());
endpoints.iter_mut().enumerate().for_each(|(id, endpoint)| {
endpoint.insert_load_pattern(load_pattern.as_ref());
endpoint.insert_special_tags(id);
endpoint.insert_global_headers(headers);
// This was done in the `from_config`` in v1.
// We need to add all loggers to all endpoints if they have a query/select
for (name, logger) in loggers {
if let Some(query) = &logger.query {
endpoint.logs.push((
name.clone(),
EndpointLogs {
query: query.clone(),
},
))
}
}
});

lt.lt_err = lt.make_lt_err();
loadtest.lt_err = loadtest.make_lt_err();

Ok(lt)
Ok(loadtest)
}

fn get_required_providers(&self) -> BTreeSet<Arc<str>> {
Expand Down
56 changes: 50 additions & 6 deletions lib/config/src/configv2/templating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use ether::{Either, Either3};
use futures::{Stream, TryStreamExt};
pub use helpers::*;
use itertools::Itertools;
use log::debug;
use serde::Deserialize;
use std::{
borrow::Cow,
Expand Down Expand Up @@ -96,7 +97,7 @@ where
} => template,
Template::PreVars { template, .. } => template,
// probably won't be needed, as serialization is done with `<VD = False>` templates.
Template::NeedsProviders { .. } => todo!(),
Template::NeedsProviders { .. } => todo!("TemplatedString FromStr NeedsProviders todo"),
}
}
}
Expand Down Expand Up @@ -205,6 +206,11 @@ impl<T: TemplateType<ProvAllowed = True>> Template<String, T, True, True> {
Ar: Clone + Send + Unpin + 'static,
E: StdError + Send + Clone + Unpin + 'static + From<EvalExprError>,
{
debug!(
"Template into_stream template={:?}, providers={:?}",
self,
providers.keys()
);
self.into_stream_with(move |p| providers.get(p).map(|p| p.as_stream()))
}

Expand Down Expand Up @@ -234,9 +240,18 @@ impl<T: TemplateType<ProvAllowed = True>> Template<String, T, True, True> {
use futures::stream::repeat;
Ok(match self {
Self::Literal { value } => {
debug!(
"Template::Literal into_stream_with value='{}', json='{}'",
value,
serde_json::Value::String(value.clone())
);
Either::A(repeat(Ok((serde_json::Value::String(value), vec![]))))
}
Self::NeedsProviders { script, .. } => {
debug!(
"Template::NeedsProviders into_stream_with script={:?}",
script
);
let streams = script
.into_iter()
.map(|s| match s {
Expand All @@ -254,13 +269,42 @@ impl<T: TemplateType<ProvAllowed = True>> Template<String, T, True, True> {
.collect::<Result<Vec<_>, _>>()?;
Either::B(zip_all::zip_all(streams).map_ok(|js| {
js.into_iter()
.map(|(j, ar)| (j.to_string().trim_matches('"').to_owned(), ar))
.map(|(j, ar)| {
match j {
serde_json::Value::String(s) => {
// We don't want to stringify the json::Value::String or it will escape out quotes, etc.
// Get the internal string and use it.
debug!("Template into_stream_with zip_all json::string s={}, to_string={}", s, s.to_string());
(s.trim_matches('"').to_owned(), ar)
},
other => {
debug!("Template into_stream_with zip_all json::other j={}, to_string={}", other, other.to_string());
(other.to_string().trim_matches('"').to_owned(), ar)
},
}
})
.reduce(|mut acc, e| {
acc.0.push_str(&e.0);
acc.1.extend(e.1);
acc
})
.map(|(s, ar)| (serde_json::Value::String(s), ar))
.map(|(s, ar)| {
// If it's an object we're turning it into a string here
(
match serde_json::from_str(&s) {
Ok(serde_json::Value::String(s)) => {
log::debug!("Template into_stream_with Using literal string {s:?} as the json value");
serde_json::Value::String(s)
}
Ok(v) => v,
Err(e) => {
log::debug!("Template into_stream_with String {s:?} is not valid JSON ({e}); reusing same string value");
serde_json::Value::String(s)
}
},
ar,
)
})
.unwrap_or_default()
}))
}
Expand Down Expand Up @@ -931,7 +975,7 @@ mod tests {
("a".to_owned().into(), VarValue::Bool(true)),
(
"b".to_owned().into(),
VarValue::List(vec![VarValue::Num(45), VarValue::Num(23)]),
VarValue::List(vec![VarValue::Num(45.0), VarValue::Num(23.0)]),
),
(
"c".to_owned().into(),
Expand All @@ -941,8 +985,8 @@ mod tests {
"d".to_owned().into(),
VarValue::Str(Template::new_literal("77".to_owned())),
),
("e".to_owned().into(), VarValue::Num(12)),
("e1".to_owned().into(), VarValue::Num(999)),
("e".to_owned().into(), VarValue::Num(12.0)),
("e1".to_owned().into(), VarValue::Num(999.0)),
]
.into(),
),
Expand Down
2 changes: 2 additions & 0 deletions tests/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ vars:
b: "foo"
e2: ["foo", "foo", "foo"]
port: "${e:PORT}"
int: 12345
float: 1.2345 # Make sure these can be parsed
providers:
a:
!range
Expand Down
Loading