#[cfg(feature = "json_tests")]
pub mod json_tests {
use indoc::indoc;
use serde::{Deserialize, Serialize};
use serde_json;
use slugify::slugify;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize)]
struct Symbol {
name: String,
value: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct StringColumn {
name: String,
value: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct LongColumn {
name: String,
value: i64,
}
#[derive(Debug, Serialize, Deserialize)]
struct DoubleColumn {
name: String,
value: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct BooleanColumn {
name: String,
value: bool,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
enum Column {
String(StringColumn),
Long(LongColumn),
Double(DoubleColumn),
Boolean(BooleanColumn),
}
#[derive(Debug, Serialize, Deserialize)]
struct Expected {
line: Option<String>,
#[serde(rename = "binaryBase64")]
binary_base64: Option<String>,
#[serde(rename = "anyLines")]
any_lines: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "UPPERCASE")]
enum Outcome {
Success(Expected),
Error,
}
#[derive(Debug, Serialize, Deserialize)]
struct TestSpec {
#[serde(rename = "testName")]
test_name: String,
table: String,
symbols: Vec<Symbol>,
columns: Vec<Column>,
result: Outcome,
}
fn parse() -> Vec<TestSpec> {
let mut json_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
json_path.push("src");
json_path.push("tests");
json_path.push("interop");
json_path.push("ilp-client-interop-test.json");
let file = std::fs::File::open(json_path).unwrap();
serde_json::from_reader(file).unwrap()
}
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
let specs = parse();
let mut file_path = PathBuf::from(std::env::var("OUT_DIR")?);
file_path.push("json_tests.rs");
let mut output = BufWriter::new(File::create(file_path)?);
writeln!(
output,
"{}",
indoc! {r#"
// This file is auto-generated by build.rs.
use crate::{Result, ingress::{Buffer, ProtocolVersion}};
use crate::tests::{TestResult};
use base64ct::Base64;
use base64ct::Encoding;
use rstest::rstest;
fn matches_any_line(line: &[u8], expected: &[&str]) -> bool {
for &exp in expected {
if line == exp.as_bytes() {
return true;
}
}
eprintln!("Could not match:\n {line:?}\nTo any of: {expected:#?}");
false
}
"#}
)?;
for (index, spec) in specs.iter().enumerate() {
writeln!(output, "/// {}", spec.test_name)?;
writeln!(output, "#[rstest]")?;
writeln!(
output,
"fn test_{:03}_{}(\n #[values(ProtocolVersion::V1, ProtocolVersion::V2)] version: ProtocolVersion,\n) -> TestResult {{",
index,
slugify!(&spec.test_name, separator = "_")
)?;
writeln!(output, " let mut buffer = Buffer::new(version);")?;
let (expected, indent) = match &spec.result {
Outcome::Success(line) => (Some(line), ""),
Outcome::Error => (None, " "),
};
if expected.is_none() {
writeln!(output, " || -> Result<()> {{")?;
}
writeln!(output, "{indent} buffer")?;
writeln!(output, "{indent} .table({:?})?", spec.table)?;
for symbol in spec.symbols.iter() {
writeln!(
output,
"{} .symbol({:?}, {:?})?",
indent, symbol.name, symbol.value
)?;
}
for column in spec.columns.iter() {
match column {
Column::String(column) => writeln!(
output,
"{} .column_str({:?}, {:?})?",
indent, column.name, column.value
)?,
Column::Long(column) => writeln!(
output,
"{} .column_i64({:?}, {:?})?",
indent, column.name, column.value
)?,
Column::Double(column) => writeln!(
output,
"{} .column_f64({:?}, {:?})?",
indent, column.name, column.value
)?,
Column::Boolean(column) => writeln!(
output,
"{} .column_bool({:?}, {:?})?",
indent, column.name, column.value
)?,
}
}
writeln!(output, "{indent} .at_now()?;")?;
if let Some(expected) = expected {
if let Some(ref base64) = expected.binary_base64 {
writeln!(output, " if version != ProtocolVersion::V1 {{")?;
writeln!(
output,
" let exp = Base64::decode_vec(\"{base64}\").unwrap();"
)?;
writeln!(
output,
" assert_eq!(buffer.as_bytes(), exp.as_slice());"
)?;
writeln!(output, " }} else {{")?;
if let Some(ref line) = expected.line {
let exp_ln = format!("{line}\n");
writeln!(output, " let exp = {exp_ln:?};")?;
writeln!(
output,
" assert_eq!(buffer.as_bytes(), exp.as_bytes());"
)?;
} else {
let any: Vec<String> = expected
.any_lines
.as_ref()
.unwrap()
.iter()
.map(|line| format!("{line}\n"))
.collect();
writeln!(output, " let any = [")?;
for line in any.iter() {
writeln!(output, " {line:?},")?;
}
writeln!(output, " ];")?;
writeln!(
output,
" assert!(matches_any_line(buffer.as_bytes(), &any));"
)?;
}
writeln!(output, " }}")?;
} else if let Some(ref line) = expected.line {
let exp_ln = format!("{line}\n");
writeln!(output, " let exp = {exp_ln:?};")?;
writeln!(output, " assert_eq!(buffer.as_bytes(), exp.as_bytes());")?;
} else {
let any: Vec<String> = expected
.any_lines
.as_ref()
.unwrap()
.iter()
.map(|line| format!("{line}\n"))
.collect();
writeln!(output, " let any = [")?;
for line in any.iter() {
writeln!(output, " {line:?},")?;
}
writeln!(output, " ];")?;
writeln!(
output,
" assert!(matches_any_line(buffer.as_bytes(), &any));"
)?;
}
} else {
writeln!(output, " Ok(())")?;
writeln!(output, " }}().unwrap_err();")?;
}
writeln!(output, " Ok(())")?;
writeln!(output, "}}")?;
}
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(not(any(feature = "tls-webpki-certs", feature = "tls-native-certs")))]
compile_error!(
"At least one of `tls-webpki-certs` or `tls-native-certs` features must be enabled."
);
#[cfg(not(any(feature = "_sender-tcp", feature = "_sender-http")))]
compile_error!(
"At least one of `sync-sender-tcp` or `sync-sender-http` features must be enabled"
);
#[cfg(not(any(feature = "aws-lc-crypto", feature = "ring-crypto")))]
compile_error!("You must enable exactly one of the `aws-lc-crypto` or `ring-crypto` features, but none are enabled.");
#[cfg(all(feature = "aws-lc-crypto", feature = "ring-crypto"))]
compile_error!("You must enable exactly one of the `aws-lc-crypto` or `ring-crypto` features, but both are enabled.");
#[cfg(feature = "json_tests")]
{
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=Cargo.lock");
println!("cargo:rerun-if-changed=src/test/interop/ilp-client-interop-test.json");
json_tests::build()?;
}
Ok(())
}