mirror of
https://github.com/icewind1991/ivory.git
synced 2026-06-03 10:44:09 +02:00
tests and optional arguments
This commit is contained in:
parent
aa1e5d7dca
commit
b8cd60fff5
10 changed files with 356 additions and 37 deletions
|
|
@ -15,4 +15,4 @@ ivory-sys = { version = "7.3", path = "sys" }
|
|||
[lib]
|
||||
name = "ivory"
|
||||
|
||||
[workspace]
|
||||
[workspace]
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ fn export_fn(item: ItemFn) -> TokenStream {
|
|||
let arg_ident = Ident::new(name, span.clone());
|
||||
quote!(
|
||||
let #arg_ident: #ty = {
|
||||
let opt: Option<#ty> = args.next().unwrap().into();
|
||||
match opt {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
::ivory::externs::error(::ivory::externs::ErrorLevel::Error, "invalid argument type,");
|
||||
let result: Result<#ty, ::ivory::CastError> = args.next().unwrap().into();
|
||||
match result {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
::ivory::externs::error(::ivory::externs::ErrorLevel::Error, format!("{}", err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,9 @@ fn export_fn(item: ItemFn) -> TokenStream {
|
|||
#[no_mangle]
|
||||
pub extern "C" fn #name(data: *const ::ivory::zend::ExecuteData, retval: *mut ::ivory::zend::ZVal) {
|
||||
let data: &::ivory::zend::ExecuteData = unsafe { data.as_ref() }.unwrap();
|
||||
if data.num_args() != #arg_count {
|
||||
// the less than case is handled during argument casting
|
||||
// this is needed for optional arguments
|
||||
if data.num_args() > #arg_count {
|
||||
::ivory::externs::error(::ivory::externs::ErrorLevel::Error, format!("unexpected number of arguments, expected {}, got {}", #arg_count, data.num_args()));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ pub mod macros;
|
|||
pub mod externs;
|
||||
pub mod info;
|
||||
pub mod zend;
|
||||
pub use crate::zend::{ArgError, ArrayKey, CastError, PhpVal};
|
||||
pub use ivory_macro::{ivory_export, ivory_module};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
pub use self::module::*;
|
||||
pub use self::function::*;
|
||||
pub use self::zval::{ExecuteData, ZVal, PhpVal};
|
||||
pub use self::module::*;
|
||||
pub use self::zval::{ArgError, ArrayKey, CastError, ExecuteData, PhpVal, ZVal};
|
||||
|
||||
mod module;
|
||||
mod function;
|
||||
mod module;
|
||||
mod zval;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::intrinsics::transmute;
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_char;
|
||||
use std::str;
|
||||
|
||||
use ivory_sys::{zend_execute_data, zval, zend_string};
|
||||
use ivory_sys::{zend_execute_data, zend_string, zval};
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct ExecuteData(zend_execute_data);
|
||||
|
|
@ -17,9 +20,7 @@ impl ExecuteData {
|
|||
fn get_arg_base(&self) -> *const ZVal {
|
||||
let offset = (size_of::<zend_execute_data>() + size_of::<zval>() - 1) / size_of::<zval>();
|
||||
let self_ptr: *const zend_execute_data = &self.0;
|
||||
unsafe {
|
||||
transmute::<_, *const ZVal>(self_ptr).add(offset)
|
||||
}
|
||||
unsafe { transmute::<_, *const ZVal>(self_ptr).add(offset) }
|
||||
}
|
||||
|
||||
pub unsafe fn get_arg(&self, i: u32) -> &ZVal {
|
||||
|
|
@ -52,7 +53,7 @@ impl Iterator for IntoArgIterator {
|
|||
self.item += 1;
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
Some(PhpVal::Undef)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +100,7 @@ impl ZVal {
|
|||
let val: PhpVal = ZVal(elem.val).as_php_val();
|
||||
match val {
|
||||
PhpVal::Undef => {}
|
||||
_ => result.push((key, val))
|
||||
_ => result.push((key, val)),
|
||||
}
|
||||
}
|
||||
result
|
||||
|
|
@ -115,7 +116,7 @@ impl ZVal {
|
|||
ZValType::Double => PhpVal::Double(unsafe { self.as_f64() }),
|
||||
ZValType::String => PhpVal::String(unsafe { self.as_str() }),
|
||||
ZValType::Array => PhpVal::Array(unsafe { self.as_array() }),
|
||||
_ => PhpVal::Undef
|
||||
_ => PhpVal::Undef,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +137,24 @@ pub enum ZValType {
|
|||
Reference = 10,
|
||||
}
|
||||
|
||||
impl Display for ZValType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ZValType::Undef => write!(f, "undefined"),
|
||||
ZValType::Null => write!(f, "null"),
|
||||
ZValType::False => write!(f, "bool"),
|
||||
ZValType::True => write!(f, "bool"),
|
||||
ZValType::Long => write!(f, "long"),
|
||||
ZValType::Double => write!(f, "double"),
|
||||
ZValType::String => write!(f, "string"),
|
||||
ZValType::Array => write!(f, "array"),
|
||||
ZValType::Object => write!(f, "object"),
|
||||
ZValType::Resource => write!(f, "resource"),
|
||||
ZValType::Reference => write!(f, "reference"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ZValType {
|
||||
fn from(val: u8) -> Self {
|
||||
if val > 10 {
|
||||
|
|
@ -151,6 +170,24 @@ pub enum ArrayKey {
|
|||
Int(u64),
|
||||
}
|
||||
|
||||
impl From<String> for ArrayKey {
|
||||
fn from(input: String) -> Self {
|
||||
ArrayKey::String(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for ArrayKey {
|
||||
fn from(input: u64) -> Self {
|
||||
ArrayKey::Int(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for ArrayKey {
|
||||
fn from(input: usize) -> Self {
|
||||
ArrayKey::Int(input as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PhpVal {
|
||||
Undef,
|
||||
|
|
@ -165,44 +202,164 @@ pub enum PhpVal {
|
|||
Reference(),
|
||||
}
|
||||
|
||||
impl PhpVal {
|
||||
pub fn get_type(&self) -> ZValType {
|
||||
match self {
|
||||
PhpVal::Undef => ZValType::Undef,
|
||||
PhpVal::Null => ZValType::Null,
|
||||
PhpVal::Bool(true) => ZValType::True,
|
||||
PhpVal::Bool(false) => ZValType::False,
|
||||
PhpVal::Long(_) => ZValType::Long,
|
||||
PhpVal::Double(_) => ZValType::Double,
|
||||
PhpVal::String(_) => ZValType::String,
|
||||
PhpVal::Array(_) => ZValType::Array,
|
||||
PhpVal::Object(_) => ZValType::Object,
|
||||
PhpVal::Resource(_) => ZValType::Resource,
|
||||
PhpVal::Reference() => ZValType::Reference,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PhpVal {
|
||||
fn default() -> Self {
|
||||
PhpVal::Undef
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhpVal> for Option<i64> {
|
||||
impl From<PhpVal> for Result<PhpVal, CastError> {
|
||||
fn from(val: PhpVal) -> Self {
|
||||
match val {
|
||||
PhpVal::Long(val) => Some(val),
|
||||
_ => None
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_phpval {
|
||||
($type:ty, $variant:ident) => {
|
||||
// non nullable version
|
||||
impl From<PhpVal> for Result<$type, CastError> {
|
||||
fn from(val: PhpVal) -> Self {
|
||||
match val {
|
||||
PhpVal::$variant(val) => Ok(val),
|
||||
_ => Err(CastError {
|
||||
actual: val.get_type(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nullable version
|
||||
impl From<PhpVal> for Result<Option<$type>, CastError> {
|
||||
fn from(val: PhpVal) -> Self {
|
||||
match val {
|
||||
PhpVal::Null => Ok(None),
|
||||
PhpVal::Undef => Ok(None),
|
||||
PhpVal::$variant(val) => Ok(Some(val)),
|
||||
_ => Err(CastError {
|
||||
actual: val.get_type(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_phpval!(i64, Long);
|
||||
impl_from_phpval!(f64, Double);
|
||||
impl_from_phpval!(bool, Bool);
|
||||
impl_from_phpval!(String, String);
|
||||
|
||||
impl From<i64> for PhpVal {
|
||||
fn from(input: i64) -> Self {
|
||||
PhpVal::Long(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for PhpVal {
|
||||
fn from(input: String) -> Self {
|
||||
PhpVal::String(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for PhpVal {
|
||||
fn from(input: bool) -> Self {
|
||||
PhpVal::Bool(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<PhpVal>> From<Option<T>> for PhpVal {
|
||||
fn from(input: Option<T>) -> Self {
|
||||
match input {
|
||||
Some(inner) => inner.into(),
|
||||
None => PhpVal::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhpVal> for Option<f64> {
|
||||
fn from(val: PhpVal) -> Self {
|
||||
match val {
|
||||
PhpVal::Double(val) => Some(val),
|
||||
_ => None
|
||||
impl<T: Into<PhpVal>> From<Vec<T>> for PhpVal {
|
||||
fn from(input: Vec<T>) -> Self {
|
||||
PhpVal::Array(
|
||||
input
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(key, value)| (key.into(), value.into()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Into<ArrayKey>, T: Into<PhpVal>> From<Vec<(K, T)>> for PhpVal {
|
||||
fn from(input: Vec<(K, T)>) -> Self {
|
||||
PhpVal::Array(
|
||||
input
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.into(), value.into()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CastError {
|
||||
actual: ZValType,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArgError {
|
||||
CastError(CastError),
|
||||
NotEnoughArguments,
|
||||
}
|
||||
|
||||
impl From<CastError> for ArgError {
|
||||
fn from(from: CastError) -> Self {
|
||||
ArgError::CastError(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CastError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Incorrect variable type, got {}", self.actual)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ArgError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ArgError::CastError(err) => err.fmt(f),
|
||||
ArgError::NotEnoughArguments => write!(f, "Not enough arugments"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhpVal> for Option<bool> {
|
||||
fn from(val: PhpVal) -> Self {
|
||||
match val {
|
||||
PhpVal::Bool(val) => Some(val),
|
||||
_ => None
|
||||
}
|
||||
impl Error for CastError {
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhpVal> for Option<String> {
|
||||
fn from(val: PhpVal) -> Self {
|
||||
match val {
|
||||
PhpVal::String(val) => Some(val),
|
||||
_ => None
|
||||
impl Error for ArgError {
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match self {
|
||||
ArgError::CastError(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
tests/Cargo.toml
Normal file
16
tests/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "ivory-tests"
|
||||
version = "0.1.0"
|
||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
ivory = { path = "../ivory", version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
maplit = "1.0"
|
||||
pretty_assertions = "0.6"
|
||||
|
||||
[lib]
|
||||
name = "tests"
|
||||
crate-type = ["dylib"]
|
||||
1
tests/README.md
Normal file
1
tests/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Tests
|
||||
53
tests/src/lib.rs
Normal file
53
tests/src/lib.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use ivory::externs::printf;
|
||||
use ivory::PhpVal;
|
||||
use ivory::{ivory_export, ivory_module};
|
||||
|
||||
fn dump<T: Debug>(arg: T) {
|
||||
printf(format!("{:?}", arg));
|
||||
}
|
||||
|
||||
#[ivory_export]
|
||||
fn dump_arg(arg: PhpVal) {
|
||||
dump(arg);
|
||||
}
|
||||
|
||||
#[ivory_export]
|
||||
fn expect_long(arg: i64) {
|
||||
dump(arg);
|
||||
}
|
||||
|
||||
#[ivory_export]
|
||||
fn expect_double(arg: f64) {
|
||||
dump(arg);
|
||||
}
|
||||
|
||||
#[ivory_export]
|
||||
fn expect_string(arg: String) {
|
||||
dump(arg);
|
||||
}
|
||||
|
||||
#[ivory_export]
|
||||
fn expect_bool(arg: bool) {
|
||||
dump(arg);
|
||||
}
|
||||
|
||||
#[ivory_export]
|
||||
fn expect_option_bool(arg: Option<bool>) {
|
||||
dump(arg);
|
||||
}
|
||||
|
||||
ivory_module!({
|
||||
name: "tests",
|
||||
version: "0.0.1",
|
||||
functions: &[
|
||||
dump_arg,
|
||||
expect_long,
|
||||
expect_double,
|
||||
expect_string,
|
||||
expect_bool,
|
||||
expect_option_bool
|
||||
],
|
||||
info: &[("test extension", "enabled")]
|
||||
});
|
||||
89
tests/tests/tests.rs
Normal file
89
tests/tests/tests.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
|
||||
use ivory::{ArrayKey, PhpVal};
|
||||
use maplit::hashmap;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[test]
|
||||
fn zval_parsing() {
|
||||
let inputs: HashMap<&str, PhpVal> = hashmap! {
|
||||
"1" => PhpVal::Long(1),
|
||||
"1.1" => PhpVal::Double(1.1),
|
||||
"\"test\"" => PhpVal::String("test".into()),
|
||||
"true" => PhpVal::Bool(true),
|
||||
"false" => PhpVal::Bool(false),
|
||||
"null" => PhpVal::Null,
|
||||
"[1,2,3]" => vec![1, 2, 3].into(),
|
||||
"[1,2,\"foo\"]" => vec![
|
||||
PhpVal::Long(1),
|
||||
PhpVal::Long(2),
|
||||
PhpVal::String("foo".into())
|
||||
].into(),
|
||||
"[1,2, 4 => 3]" => vec![
|
||||
(0u64, 1),
|
||||
(1, 2),
|
||||
(4, 3)
|
||||
].into(),
|
||||
"[1,2, \"foo\" => 3]" => vec![
|
||||
(ArrayKey::from(0u64), 1),
|
||||
(ArrayKey::from(1u64), 2),
|
||||
(ArrayKey::from("foo".to_string()), 3)
|
||||
].into(),
|
||||
};
|
||||
|
||||
for (input, expected) in inputs {
|
||||
let code = format!("dump_arg({})", input);
|
||||
let result = run_php(&code).unwrap();
|
||||
assert_debug_eq(expected, &result);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! test_cast {
|
||||
($name:ident, $method:expr, $in:expr, $fail:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let result = run_php(&format!("{}({})", $method, $in)).unwrap();
|
||||
assert_debug_eq($in, &result);
|
||||
assert_eq!(true, run_php(&format!("{}({})", $method, $fail)).is_err());
|
||||
assert_eq!(true, run_php(&format!("{}(null)", $method)).is_err());
|
||||
assert_eq!(true, run_php(&format!("{}()", $method)).is_err());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_cast!(test_cast_long, "expect_long", 1, false);
|
||||
test_cast!(test_cast_double, "expect_double", 1.1, false);
|
||||
test_cast!(test_cast_string, "expect_string", "foo".to_string(), false);
|
||||
test_cast!(test_cast_bool, "expect_bool", true, 17);
|
||||
|
||||
#[test]
|
||||
fn test_cast_option() {
|
||||
let result = run_php("expect_option_bool(true)").unwrap();
|
||||
assert_debug_eq(Some(true), &result);
|
||||
assert_eq!(true, run_php("expect_option_bool(17)").is_err());
|
||||
let result = run_php("expect_option_bool(null)").unwrap();
|
||||
assert_debug_eq::<Option<bool>>(None, &result);
|
||||
let result = run_php("expect_option_bool()").unwrap();
|
||||
assert_debug_eq::<Option<bool>>(None, &result);
|
||||
}
|
||||
|
||||
/// Test that the result is the debug formatting of expected
|
||||
fn assert_debug_eq<T: Debug>(expected: T, result: &str) {
|
||||
assert_eq!(format!("{:?}", expected), result);
|
||||
}
|
||||
|
||||
/// Run some php code and return it's output
|
||||
fn run_php(code: &str) -> Result<String, String> {
|
||||
let code = format!("{};", code);
|
||||
let output = Command::new("php")
|
||||
.args(&["-d", "extension=target/debug/libtests.so", "-r", &code])
|
||||
.output()
|
||||
.expect("Failed to run php script");
|
||||
if output.status.success() {
|
||||
Ok(String::from_utf8(output.stdout).expect("invalid utf8"))
|
||||
} else {
|
||||
Err(String::from_utf8(output.stderr).expect("invalid utf8"))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue