Initial commit
commit
d674506717
@ -0,0 +1 @@
|
||||
/target
|
@ -0,0 +1,213 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flb_lua_tester"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"mlua",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "lua-src"
|
||||
version = "544.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luajit-src"
|
||||
version = "210.4.5+resty2cf5186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b7992a40e602786272d84c6f2beca44a588ededcfd57b48ec6f82008a7cb97"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea8ce6788556a67d90567809c7de94dfef2ff1f47ff897aeee935bcfbcdf5735"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"cc",
|
||||
"erased-serde",
|
||||
"lua-src",
|
||||
"luajit-src",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
|
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "flb_lua_tester"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.8.8", features = ["luajit", "vendored", "serialize" ] }
|
||||
serde = { version = "1.0.162", features = ["derive"] }
|
||||
serde_yaml = "0.9.21"
|
@ -0,0 +1,70 @@
|
||||
# Fluent Bit Lua Tester
|
||||
|
||||
A tool to test Lua scripts written for Fluent Bit filters. It calls the function and expects return values using the same interface as Fluent Bit does, using LuaJIT like Fluent Bit does as well.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This tool is a very early experiment, so it doesn't have any release infrastructure. The only way to run it currently is to have Rust installed and build/run it with `cargo`.
|
||||
|
||||
The tool accepts the path to the yaml test config as the first argument to the program.
|
||||
|
||||
## Example
|
||||
|
||||
Let's test an extremely simple Lua script `example.lua` that we are using as a Fluent Bit Filter:
|
||||
|
||||
```lua
|
||||
function filter_entry(tag, timestamp, record)
|
||||
record["y"] = "z"
|
||||
return 0, timestamp, record
|
||||
end
|
||||
```
|
||||
|
||||
The test config yaml file specifies a list of Lua scripts to test, in each:
|
||||
* The file path (relative to the working directory of the binary)
|
||||
* The function in the script to call
|
||||
* An array of tests each with
|
||||
- Test case name
|
||||
- The input arguments (tag, timestamp, and record)
|
||||
- The expected output result (code, timestamp, and record)
|
||||
|
||||
Let's write a small set of unit tests for this script in `example_test.yaml`:
|
||||
|
||||
```yaml
|
||||
scripts:
|
||||
- file: "example.lua"
|
||||
call: "filter_entry"
|
||||
tests:
|
||||
- name: "adds y key"
|
||||
input:
|
||||
tag: "hi"
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
w: x
|
||||
expected:
|
||||
code: 0
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
w: x
|
||||
y: z
|
||||
- name: "resets existing y key"
|
||||
input:
|
||||
tag: "hi"
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
y: something else
|
||||
expected:
|
||||
code: 0
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
y: z
|
||||
```
|
||||
|
||||
Run this test with the command `cargo run -- example_test.yaml`
|
||||
|
||||
```
|
||||
Running test: "adds y key"
|
||||
Test Passed
|
||||
|
||||
Running test: "resets existing y key"
|
||||
Test Passed
|
||||
```
|
@ -0,0 +1,5 @@
|
||||
|
||||
function filter_entry(tag, timestamp, record)
|
||||
record["y"] = "z"
|
||||
return 0, timestamp, record
|
||||
end
|
@ -0,0 +1,27 @@
|
||||
scripts:
|
||||
- file: "examples/example.lua"
|
||||
call: "filter_entry"
|
||||
tests:
|
||||
- name: "adds y key"
|
||||
input:
|
||||
tag: "hi"
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
w: x
|
||||
expected:
|
||||
code: 0
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
w: x
|
||||
y: z
|
||||
- name: "resets existing y key"
|
||||
input:
|
||||
tag: "hi"
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
y: something else
|
||||
expected:
|
||||
code: 0
|
||||
timestamp: "2014-10-02T15:01:23Z"
|
||||
record:
|
||||
y: z
|
@ -0,0 +1,83 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
|
||||
use mlua::{ToLuaMulti, LuaSerdeExt};
|
||||
use mlua::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
pub scripts: Vec<ScriptTest>,
|
||||
}
|
||||
|
||||
pub fn load_config(path: String) -> Config {
|
||||
let f = File::open(path).expect("Could not open file.");
|
||||
return serde_yaml::from_reader(f).expect("Could not read values.");
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ScriptTest {
|
||||
pub file: String,
|
||||
pub call: String,
|
||||
pub tests: Vec<TestCase>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct TestCase {
|
||||
pub name: String,
|
||||
pub input: LuaFnInput,
|
||||
pub expected: LuaFnOutput,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct LuaFnInput {
|
||||
pub tag: String,
|
||||
pub timestamp: String,
|
||||
pub record: HashMap<String, FlbRecordValidType>,
|
||||
}
|
||||
|
||||
impl<'lua> ToLuaMulti<'lua> for LuaFnInput {
|
||||
fn to_lua_multi(self, lua: &'lua mlua::Lua) -> mlua::Result<mlua::MultiValue<'lua>> {
|
||||
let mut mv = mlua::MultiValue::new();
|
||||
|
||||
let record = lua.create_table().unwrap();
|
||||
for row in self.record.iter() {
|
||||
let key = lua.to_value(row.0).unwrap();
|
||||
let val = lua.to_value(row.1).unwrap();
|
||||
record.set(key, val).unwrap();
|
||||
}
|
||||
mv.push_front(mlua::Value::Table(record));
|
||||
|
||||
mv.push_front(self.timestamp.to_lua(lua).unwrap());
|
||||
|
||||
mv.push_front(self.tag.to_lua(lua).unwrap());
|
||||
|
||||
return Ok(mv);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct LuaFnOutput {
|
||||
pub code: i64,
|
||||
pub timestamp: String,
|
||||
pub record: HashMap<String, FlbRecordValidType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum FlbRecordValidType {
|
||||
String(String),
|
||||
Number(f64),
|
||||
Table(HashMap<String, FlbRecordValidType>),
|
||||
}
|
||||
|
||||
impl PartialEq for FlbRecordValidType {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::String(l0), Self::String(r0)) => l0 == r0,
|
||||
(Self::Number(l0), Self::Number(r0)) => l0 == r0,
|
||||
(Self::Table(l0), Self::Table(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
pub mod config;
|
@ -0,0 +1,75 @@
|
||||
mod config;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
|
||||
use config::config::FlbRecordValidType;
|
||||
use mlua::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut args = env::args();
|
||||
if args.len() == 1 {
|
||||
println!("Please provide a config file path.");
|
||||
return;
|
||||
}
|
||||
let path = args.nth(1).unwrap();
|
||||
let config = config::config::load_config(path);
|
||||
|
||||
for script in config.scripts {
|
||||
let lua = load_script(script.file);
|
||||
let f_res = lua.globals().get::<_, mlua::Function>(script.call);
|
||||
if f_res.is_err() {
|
||||
continue;
|
||||
}
|
||||
let f = f_res.unwrap();
|
||||
for tc in script.tests {
|
||||
println!("Running test: {:?}", tc.name);
|
||||
let mut test_passed = true;
|
||||
|
||||
let r = f.call::<_, LuaMultiValue>(tc.input);
|
||||
let mut mv = r.unwrap();
|
||||
|
||||
let code = i64::from_lua(mv.pop_front().unwrap(), &lua).unwrap();
|
||||
if code != tc.expected.code {
|
||||
test_passed = false;
|
||||
println!(" expected code: {:?}", tc.expected.code);
|
||||
println!(" got code: {:?}\n", code);
|
||||
}
|
||||
|
||||
let timestamp = String::from_lua(mv.pop_front().unwrap(), &lua).unwrap();
|
||||
if timestamp != tc.expected.timestamp {
|
||||
test_passed = false;
|
||||
println!(" expected timestamp: {:?}", tc.expected.timestamp);
|
||||
println!(" got timestamp: {:?}\n", timestamp);
|
||||
}
|
||||
|
||||
let record_value: LuaValue = mv.pop_front().unwrap();
|
||||
let record: HashMap<String, FlbRecordValidType> = lua.from_value(record_value).unwrap();
|
||||
if record != tc.expected.record {
|
||||
test_passed = false;
|
||||
println!(" expected record: {:?}", tc.expected.record);
|
||||
println!(" got record: {:?}\n", record);
|
||||
}
|
||||
|
||||
if test_passed {
|
||||
println!("Test Passed\n");
|
||||
} else {
|
||||
println!("Test Failed\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_script(script_path: String) -> Lua {
|
||||
let lua = Lua::new();
|
||||
let mut script_content = "".to_string();
|
||||
let file = File::open(script_path).unwrap();
|
||||
for line in io::BufReader::new(file).lines() {
|
||||
script_content += &line.unwrap();
|
||||
script_content += "\n";
|
||||
}
|
||||
lua.load(&script_content).exec().unwrap();
|
||||
return lua;
|
||||
}
|
Loading…
Reference in New Issue