diff --git a/python-parser/.gitignore b/python-parser/.gitignore new file mode 100644 index 0000000..10220eb --- /dev/null +++ b/python-parser/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +*.pyc diff --git a/python-parser/Cargo.toml b/python-parser/Cargo.toml new file mode 100644 index 0000000..7008dcf --- /dev/null +++ b/python-parser/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "python-parser" +version = "0.2.0" +authors = ["Tibor Benke "] +build = "build.rs" + +[lib] +crate-type = ["rlib", "dylib"] + +[dependencies] +syslog-ng-common = "0.8" +cpython = { git = "https://github.com/ihrwein/rust-cpython.git" } +log = "0.3" +env_logger = "0.3" + +[build-dependencies] +syslog-ng-build = "0.2" diff --git a/python-parser/README.md b/python-parser/README.md new file mode 100644 index 0000000..081e163 --- /dev/null +++ b/python-parser/README.md @@ -0,0 +1,95 @@ +# Python parser for syslog-ng + +For a real world tested example, check the `_test_module/regex.py` file. + +A Python parser is Python class which implements two methods: +* `init(self, options)`: (optional) After the parser instance was created, this method + is called on it. The `options` variable is a dictionary with key-value pairs +* `parse(self, logmsg, message)`: (mandatory) This method is called upon receiving + a new log mesage. The first, `logmsg` parameter is a dictionary-like data-structure + which contains the already parsed key-value pairs. You can fetch the existing ones + or insert new ones with the `__getitem__/__setitem__` methods. + +Example: +```python +import re + +class RegexParser: + def init(self, options): + pattern = options["regex"] + self.regex = re.compile(pattern) + + def parse(self, logmsg, message): + match = self.regex.match(message) + if match is not None: + for key, value in match.groupdict().items(): + logmsg[key] = value + return True + else: + return False +``` + +If an exception is thrown during `init()` is is considered an initialization error and syslog-ng won't be started. + +## Configuration + + +``` +@version: 3.8 + +block parser regex( + regex("") +) +{ + regex-rs( + option("regex", `regex`) + ); +}; + +source s_localhost { + network( + ip( + 127.0.0.1 + ), + port( + 1514 + ), + transport("tcp") + ); +}; + +log { + source( + s_localhost + ); + parser { + python-rs( + option("module", "_test_module.regex") + option("class", "RegexParser") + option("regex", "seq: (?P\\d+), thread: (?P\\d+), runid: (?P\\d+), stamp: (?P[^ ]+) (?P.*$)") + ); + }; + destination { + file("/dev/stdout" template("runid=$runid\n")); + }; +}; +``` + +Make sure, that you can import the `_test_module.regex` module +from a Python shell. If not, you can add its directory to +the `PYTHONPATH` environment variable: + +``` +PYTHONPATH=/home/tibi/workspace/python-parser sbin/syslog-ng -Fevd +``` + +## Compilation + +You need a nightly Rust compiler. +Make sure, pkg-config is able to find syslog-ng and `libsyslog-ng.so` is in your +library path. + +``` +cargo build --release +cp target/release/libpython_parser.so /lib/syslog-ng/ +``` diff --git a/python-parser/_test_module/__init__.py b/python-parser/_test_module/__init__.py new file mode 100644 index 0000000..1cb16a7 --- /dev/null +++ b/python-parser/_test_module/__init__.py @@ -0,0 +1,46 @@ +# Keep this class commented out +# class NonExistingParser: pass + +class ExistingParser: pass + +class CallableClass: pass + +NotCallableObject = int() + +class ClassWithInitMethod: + def init(self, options): + pass + +class InitMethodReturnsNotNone: + def init(self, options): + return True + +class ParserWithoutInitMethod: pass + +class ParserClassWithGoodParseMethod: + def parse(self, logmsg, input): + return True + +class ParserWithoutParseMethod: pass + +class ParseMethodReturnsNotBoolean: + def parse(self, logmsg, input): + return None + +class ParseReturnsTrue: + def parse(self, logmsg, input): + return True + +class ParseReturnsFalse: + def parse(self, logmsg, input): + return False + +class ExceptionIsRaisedInParseMethod: + def parse(self, logmsg, input): + raise TypeError("text") + return False + +class ExceptionIsRaisedInInitMethod: + def init(self, options): + raise TypeError("text") + return True diff --git a/python-parser/_test_module/regex.py b/python-parser/_test_module/regex.py new file mode 100644 index 0000000..880d384 --- /dev/null +++ b/python-parser/_test_module/regex.py @@ -0,0 +1,15 @@ +import re + +class RegexParser: + def init(self, options): + pattern = options["regex"] + self.regex = re.compile(pattern) + + def parse(self, logmsg, message): + match = self.regex.match(message) + if match is not None: + for key, value in match.groupdict().items(): + logmsg[key] = value + return True + else: + return False diff --git a/python-parser/benches/parse.rs b/python-parser/benches/parse.rs new file mode 100644 index 0000000..d864b17 --- /dev/null +++ b/python-parser/benches/parse.rs @@ -0,0 +1,26 @@ +#![feature(test)] +extern crate test; +extern crate python_parser; +extern crate syslog_ng_common; +extern crate env_logger; + +use std::env; +use test::Bencher; +use syslog_ng_common::{LogMessage, Parser}; +use python_parser::utils::build_parser_with_options; + +use syslog_ng_common::sys::logmsg::log_msg_registry_init; + +#[bench] +fn bench_parse(b: &mut Bencher) { + unsafe { + log_msg_registry_init(); + }; + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let options = [("regex", r#"seq: (?P\d+), thread: (?P\d+), runid: (?P\d+), stamp: (?P[^ ]+) (?P.*$)"#)]; + let message = "seq: 0000000000, thread: 0000, runid: 1456947132, stamp: 2016-03-02T20:32:12 PAD"; + let mut parser = build_parser_with_options("_test_module.regex", "RegexParser", &options); + let mut logmsg = LogMessage::new(); + b.iter(|| parser.parse(&mut logmsg, message)); +} diff --git a/python-parser/build.rs b/python-parser/build.rs new file mode 100644 index 0000000..e33c528 --- /dev/null +++ b/python-parser/build.rs @@ -0,0 +1,8 @@ +extern crate syslog_ng_build; + +fn main() { + let canonical_name = "python-parser"; + let description = "This is a Python parser written in Rust"; + let parser_name = "python-rs"; + syslog_ng_build::create_module(canonical_name, description, Some(parser_name)); +} diff --git a/python-parser/src/lib.rs b/python-parser/src/lib.rs new file mode 100644 index 0000000..aa04102 --- /dev/null +++ b/python-parser/src/lib.rs @@ -0,0 +1,180 @@ +#[macro_use] +extern crate syslog_ng_common; +#[macro_use] +extern crate log; +#[macro_use] +extern crate cpython; + +pub mod py_logmsg; +pub mod utils; + +use std::borrow::Borrow; +use std::marker::PhantomData; + +use syslog_ng_common::{LogMessage, Parser, ParserBuilder, OptionError, Pipe, GlobalConfig}; +use cpython::{Python, PyDict, NoArgs, PyClone, PyObject, PyResult, PyModule, PyErr, PyString}; +use cpython::ObjectProtocol; //for call method +use cpython::exc::TypeError; + +pub use py_logmsg::PyLogMessage; + +pub mod options { + pub const MODULE: &'static str = "module"; + pub const CLASS: &'static str = "class"; +} + +pub struct PythonParser { + parser: PyObject, + _marker: PhantomData

+} + +impl Clone for PythonParser

{ + fn clone(&self) -> Self { + let gil = Python::acquire_gil(); + let py = gil.python(); // obtain `Python` token + PythonParser {parser: self.parser.clone_ref(py), _marker: PhantomData} + } +} + +pub struct PythonParserBuilder { + module: Option, + class: Option, + options: Vec<(String, String)>, + _marker: PhantomData

+} + +impl PythonParserBuilder

{ + // Although these functions are very small ones, they are very useful for testing + pub fn load_module<'p>(py: Python<'p>, module_name: &str) -> PyResult { + debug!("Trying to load Python module, module='{}'", module_name); + py.import(module_name) + } + pub fn load_class<'p>(py: Python<'p>, module: &PyModule, class_name: &str) -> PyResult { + debug!("Trying to load Python class, class='{}'", class_name); + module.get(py, class_name) + } + pub fn instantiate_class<'p>(py: Python<'p>, class: &PyObject) -> PyResult { + debug!("Trying to instantiate Python parser"); + class.call(py, NoArgs, None) + } + pub fn create_options_dict<'p>(py: Python<'p>, init_options: &[(String, String)]) -> PyResult { + debug!("Instantiating the options dict"); + let options = PyDict::new(py); + for &(ref k, ref v) in init_options { + debug!("Adding values to the options dict, key='{}', value='{}'", k, v); + try!(options.set_item(py, k, v)); + } + Ok(options) + } + fn call_init<'p>(py: Python<'p>, instance: &PyObject, options: PyDict) -> PyResult<()> { + let init_result = try!(instance.call_method(py, "init", (&options, ), None)); + if init_result == Python::None(py) { + Ok(()) + } else { + let errmsg = PyString::new(py, "The init() method mustn't return any value"); + Err(PyErr::new::(py, errmsg)) + } + } + pub fn initialize_instance<'p>(py: Python<'p>, instance: &PyObject, options: PyDict) -> PyResult<()> { + debug!("Trying to call init() on the Python parser instance"); + if try!(instance.hasattr(py, "init")) { + Self::call_init(py, instance, options) + } else { + Ok(()) + } + } + pub fn initialize_class<'p>(py: Python<'p>, class: &PyObject, options: &[(String, String)]) -> PyResult { + let parser_instance = try!(Self::instantiate_class(py, &class)); + let options = try!(Self::create_options_dict(py, options)); + let _ = try!(Self::initialize_instance(py, &parser_instance, options)); + Ok(parser_instance) + } + + pub fn load_and_init_class<'p>(py: Python<'p>, module_name: &str, class_name: &str, options: &[(String, String)]) -> PyResult { + let module = try!(Self::load_module(py, module_name)); + let class = try!(Self::load_class(py, &module, class_name)); + Self::initialize_class(py, &class, options) + } +} + +impl ParserBuilder

for PythonParserBuilder

{ + type Parser = PythonParser

; + fn new(_: GlobalConfig) -> Self { + PythonParserBuilder { + module: None, + class: None, + options: Vec::new(), + _marker: PhantomData + } + } + fn option(&mut self, name: String, value: String) { + match name.borrow() { + options::MODULE => { self.module = Some(value); }, + options::CLASS => { self.class = Some(value); }, + _ => { self.options.push((name, value)); } + } + } + fn build(self) -> Result { + let gil = Python::acquire_gil(); + let py = gil.python(); // obtain `Python` token + + match (self.module, self.class) { + (Some(ref module_name), Some(ref class_name)) => { + match PythonParserBuilder::

::load_and_init_class(py, module_name, class_name, &self.options) { + Ok(parser_instance) => { + debug!("Python parser successfully initialized, class='{}'", &class_name); + Ok(PythonParser {parser: parser_instance, _marker: PhantomData}) + }, + Err(error) => { + error!("Failed to create Python parser, class='{}'", class_name); + Err(OptionError::verbatim_error(format!("{:?}", error))) + } + } + }, + (ref module, ref class) => { + error!("Missing parameters in Python parser: module={:?}, class={:?}", module, class); + Err(OptionError::missing_required_option("module")) + } + } + } +} + +impl PythonParser

{ + pub fn process_parsing<'p>(&mut self, py: Python<'p>, logmsg: PyLogMessage, message: &str) -> PyResult { + debug!("Trying to call parse() method on Python parser"); + self.parser.call_method(py, "parse", (logmsg, message), None) + } + pub fn process_parse_result<'p>(py: Python<'p>, result: PyObject) -> PyResult { + debug!("Trying to check the result of parse()"); + result.extract::(py) + } + pub fn call_parse<'p>(&mut self, py: Python<'p>, logmsg: PyLogMessage, input: &str) -> PyResult { + let result = try!(self.process_parsing(py, logmsg, input)); + PythonParser::

::process_parse_result(py, result) + } +} + +impl Parser

for PythonParser

{ + fn parse(&mut self, _: &mut P, logmsg: &mut LogMessage, input: &str) -> bool { + let gil = Python::acquire_gil(); + let py = gil.python(); + match PyLogMessage::new(py, logmsg.clone()) { + Ok(pylogmsg) => { + match self.call_parse(py, pylogmsg, input) { + Ok(result) => result, + Err(error) => { + error!("Failed to extract return value of parse() method: {:?}", error); + false + } + } + }, + // I didn't find a way to test this case :-( + Err(error) => { + error!("Failed to create PyLogMessage: {:?}", error); + false + } + } + } +} + +parser_plugin!(PythonParserBuilder); diff --git a/python-parser/src/py_logmsg.rs b/python-parser/src/py_logmsg.rs new file mode 100644 index 0000000..208fcd4 --- /dev/null +++ b/python-parser/src/py_logmsg.rs @@ -0,0 +1,44 @@ +use syslog_ng_common::LogMessage; + +use cpython::{Python, ToPyObject, NoArgs,PyObject, PyResult, PyString}; +use cpython::rustobject::{TypeBuilder, PyRustObject}; +use cpython::ObjectProtocol; //for call method + +fn getitem(py: Python, slf: &PyRustObject, arg: &str) -> PyResult { + if let Some(value) = slf.get(py).get(arg) { + let value = String::from_utf8_lossy(value); + Ok(PyString::new(py, &value)) + } else { + Ok(PyString::new(py, "")) + } +} + +fn setitem(py: Python, slf: &PyRustObject, key: &str, value: &str) -> PyResult { + let msg = slf.get_mut(py); + msg.insert(key, value.as_bytes()); + Ok(NoArgs) +} + +pub struct PyLogMessage(PyRustObject); + +impl PyLogMessage { + pub fn new<'p>(py: Python<'p>, logmsg: LogMessage) -> PyResult { + let mut b = TypeBuilder::::new(py, "PyLogMessage"); + b.add("__getitem__", py_method!(getitem(arg: &str))); + b.add("__setitem__", py_method!(setitem(key: &str, value: &str))); + trace!("Trying to finish construction PyLogMessage"); + let built_type = try!(b.finish()); + let instance = built_type.create_instance(py, logmsg, ()); + Ok(PyLogMessage(instance)) + } +} + +impl ToPyObject for PyLogMessage { + type ObjectType = PyObject; + fn to_py_object(&self, py: Python) -> Self::ObjectType { + self.0.to_py_object(py) + } + fn into_py_object(self, _py: Python) -> PyObject { + self.0.into_py_object(_py) + } +} diff --git a/python-parser/src/utils.rs b/python-parser/src/utils.rs new file mode 100644 index 0000000..c4bdc10 --- /dev/null +++ b/python-parser/src/utils.rs @@ -0,0 +1,19 @@ +use syslog_ng_common::{ParserBuilder, mock, GlobalConfig}; + +use PythonParser; +use PythonParserBuilder; + +pub fn build_parser_with_options(module_name: &str, class_name: &str, options: &[(&str, &str)]) -> PythonParser { + let cfg = GlobalConfig::new(0x0308); + let mut builder = PythonParserBuilder::new(cfg); + builder.option(::options::MODULE.to_owned(), module_name.to_owned()); + builder.option(::options::CLASS.to_owned(), class_name.to_owned()); + for &(k, v) in options { + builder.option(k.to_owned(), v.to_owned()); + } + builder.build().unwrap() +} + +pub fn build_parser(module_name: &str, class_name: &str) -> PythonParser { + build_parser_with_options(module_name, class_name, &[]) +} diff --git a/python-parser/tests/parser.rs b/python-parser/tests/parser.rs new file mode 100644 index 0000000..9057cea --- /dev/null +++ b/python-parser/tests/parser.rs @@ -0,0 +1,101 @@ +extern crate python_parser; +extern crate syslog_ng_common; +extern crate cpython; +extern crate env_logger; + +use std::env; +use python_parser::{PythonParser, PyLogMessage}; +use python_parser::utils::*; +use syslog_ng_common::{ParserBuilder, LogMessage, Parser}; +use syslog_ng_common::mock::MockPipe; +use syslog_ng_common::sys::logmsg::log_msg_registry_init; +use cpython::{Python, PyResult, PyObject}; + +const TEST_MODULE_NAME: &'static str = "_test_module"; + + +fn call_parse<'p>(py: Python<'p>, module_name: &str, class_name: &str) -> PyResult { + let mut parser = build_parser(module_name, class_name); + let logmsg = LogMessage::new(); + let pylogmsg = PyLogMessage::new(py, logmsg).unwrap(); + parser.process_parsing(py, pylogmsg, "input message to be parsed") +} + +#[test] +fn test_parse_method_can_be_called() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let _ = call_parse(py, TEST_MODULE_NAME, "ParserClassWithGoodParseMethod").unwrap(); +} + +#[test] +fn test_error_is_returned_if_there_is_no_parse_method() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let result = call_parse(py, TEST_MODULE_NAME, "ParseMethodReturnsNotBoolean").unwrap(); + let _ = PythonParser::::process_parse_result(py, result).err().unwrap(); +} + +#[test] +fn test_parse_method_which_returns_boolean_does_not_raise_errors() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let result = call_parse(py, TEST_MODULE_NAME, "ParserClassWithGoodParseMethod").unwrap(); + let _ = PythonParser::::process_parse_result(py, result).unwrap(); +} + +#[test] +fn test_successful_parse() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let mut parser = build_parser(TEST_MODULE_NAME, "ParseReturnsTrue"); + let mut logmsg = LogMessage::new(); + let mut pipe = MockPipe::new(); + assert_eq!(true, parser.parse(&mut pipe, &mut logmsg, "input message to be parsed")); +} + +#[test] +fn test_unsucessful_parse() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let mut parser = build_parser(TEST_MODULE_NAME, "ParseReturnsFalse"); + let mut logmsg = LogMessage::new(); + let mut pipe = MockPipe::new(); + assert_eq!(false, parser.parse(&mut pipe, &mut logmsg, "input message to be parsed")); +} + +#[test] +fn test_parse_method_raises_an_exception() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let mut parser = build_parser(TEST_MODULE_NAME, "ExceptionIsRaisedInParseMethod"); + let mut logmsg = LogMessage::new(); + let mut pipe = MockPipe::new(); + assert_eq!(false, parser.parse(&mut pipe, &mut logmsg, "input message to be parsed")); +} + +#[test] +fn test_regex_parser() { + unsafe { + log_msg_registry_init(); + }; + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let options = [("regex", r#"seq: (?P\d+), thread: (?P\d+), runid: (?P\d+), stamp: (?P[^ ]+) (?P.*$)"#)]; + let message = "seq: 0000000000, thread: 0000, runid: 1456947132, stamp: 2016-03-02T20:32:12 PAD"; + let mut parser = build_parser_with_options("_test_module.regex", "RegexParser", &options); + let mut logmsg = LogMessage::new(); + let mut pipe = MockPipe::new(); + assert_eq!(true, parser.parse(&mut pipe, &mut logmsg, message)); + assert_eq!(b"0000000000", logmsg.get("seq").unwrap()); + assert_eq!(b"0000", logmsg.get("thread").unwrap()); + assert_eq!(b"1456947132", logmsg.get("runid").unwrap()); + assert_eq!(b"2016-03-02T20:32:12", logmsg.get("stamp").unwrap()); + assert_eq!(b"PAD", logmsg.get("padding").unwrap()); +} diff --git a/python-parser/tests/parser_builder.rs b/python-parser/tests/parser_builder.rs new file mode 100644 index 0000000..0bd57be --- /dev/null +++ b/python-parser/tests/parser_builder.rs @@ -0,0 +1,161 @@ +extern crate python_parser; +extern crate syslog_ng_common; +extern crate cpython; +extern crate env_logger; + +use std::env; +use python_parser::{PythonParserBuilder, options}; +use syslog_ng_common::{ParserBuilder, SYSLOG_NG_INITIALIZED, syslog_ng_global_init, GlobalConfig}; +use syslog_ng_common::mock::MockPipe; +use cpython::{Python, PyDict}; + +const TEST_MODULE_NAME: &'static str = "_test_module"; + +#[test] +fn test_exising_module_can_be_imported() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let _ = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); +} + +#[test] +fn test_non_exising_module_cannot_be_imported() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let _ = PythonParserBuilder::::load_module(py, "__non_existing_python_module_name").err().unwrap(); +} + +#[test] +fn test_existing_class_be_imported_from_module() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let _ = PythonParserBuilder::::load_class(py, &module, "ExistingParser").unwrap(); +} + +#[test] +fn test_non_exising_class_cannot_be_imported() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let _ = PythonParserBuilder::::load_class(py, &module, "NonExistingParser").err().unwrap(); +} + +#[test] +fn test_parser_class_is_callable() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let class = PythonParserBuilder::::load_class(py, &module, "CallableClass").unwrap(); + let _ = PythonParserBuilder::::instantiate_class(py, &class).unwrap(); +} + +#[test] +fn test_not_callable_object_cannot_be_instantiated() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let class = PythonParserBuilder::::load_class(py, &module, "NotCallableObject").unwrap(); + let _ = PythonParserBuilder::::instantiate_class(py, &class).err().unwrap(); +} + +#[test] +fn test_init_is_called_if_it_exists() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let class = PythonParserBuilder::::load_class(py, &module, "ClassWithInitMethod").unwrap(); + let instance = PythonParserBuilder::::instantiate_class(py, &class).unwrap(); + let _ = PythonParserBuilder::::initialize_instance(py, &instance, PyDict::new(py)).unwrap(); +} + +#[test] +fn test_parser_may_not_have_init_method() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let class = PythonParserBuilder::::load_class(py, &module, "InitMethodReturnsNotNone").unwrap(); + let instance = PythonParserBuilder::::instantiate_class(py, &class).unwrap(); + let _ = PythonParserBuilder::::initialize_instance(py, &instance, PyDict::new(py)).err().unwrap(); +} + +#[test] +fn test_init_must_return_nothing() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let module = PythonParserBuilder::::load_module(py, TEST_MODULE_NAME).unwrap(); + let class = PythonParserBuilder::::load_class(py, &module, "ParserWithoutInitMethod").unwrap(); + let instance = PythonParserBuilder::::instantiate_class(py, &class).unwrap(); + let _ = PythonParserBuilder::::initialize_instance(py, &instance, PyDict::new(py)).unwrap(); +} + +#[test] +fn test_module_loading_and_class_initialization() { + let _ = env_logger::init(); + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + let gil = Python::acquire_gil(); + let py = gil.python(); + let options = []; + let _ = PythonParserBuilder::::load_and_init_class(py, "__non_existing_python_module_name", "ExistingParser", &options).err().unwrap(); + let _ = PythonParserBuilder::::load_and_init_class(py, TEST_MODULE_NAME, "NonExistingParser", &options).err().unwrap(); + let _ = PythonParserBuilder::::load_and_init_class(py, TEST_MODULE_NAME, "ExistingParser", &options).unwrap(); + let _ = PythonParserBuilder::::load_and_init_class(py, TEST_MODULE_NAME, "ClassWithInitMethod", &options).unwrap(); + let _ = PythonParserBuilder::::load_and_init_class(py, TEST_MODULE_NAME, "InitMethodReturnsNotNone", &options).err().unwrap(); +} + +#[test] +fn test_parser_can_be_built_if_there_is_no_error() { + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + SYSLOG_NG_INITIALIZED.call_once(|| { + unsafe { syslog_ng_global_init(); } + }); + let cfg = GlobalConfig::new(0x0308); + let mut builder = PythonParserBuilder::::new(cfg); + builder.option(options::MODULE.to_owned(), "_test_module".to_owned()); + builder.option(options::CLASS.to_owned(), "ExistingParser".to_owned()); + let _ = builder.build().unwrap(); +} + +#[test] +fn test_parser_cannot_be_built_if_there_is_an_error() { + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + SYSLOG_NG_INITIALIZED.call_once(|| { + unsafe { syslog_ng_global_init(); } + }); + let cfg = GlobalConfig::new(0x0308); + let mut builder = PythonParserBuilder::::new(cfg); + builder.option(options::MODULE.to_owned(), "_test_module".to_owned()); + builder.option(options::CLASS.to_owned(), "NonExistingParser".to_owned()); + let _ = builder.build().err().unwrap(); +} + +#[test] +fn test_exception_is_raised_in_init_method() { + env::set_var("PYTHONPATH", env::current_dir().unwrap()); + SYSLOG_NG_INITIALIZED.call_once(|| { + unsafe { syslog_ng_global_init(); } + }); + let cfg = GlobalConfig::new(0x0308); + let mut builder = PythonParserBuilder::::new(cfg); + builder.option(options::MODULE.to_owned(), "_test_module".to_owned()); + builder.option(options::CLASS.to_owned(), "ExceptionIsRaisedInInitMethod".to_owned()); + let _ = builder.build().err().unwrap(); +}