Skip to content

Commit

Permalink
pytest marks
Browse files Browse the repository at this point in the history
  • Loading branch information
brownben committed Nov 8, 2024
1 parent 6df1a65 commit 7a59efe
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 4 deletions.
46 changes: 46 additions & 0 deletions examples/pytest_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
Test the ways to skip a test using `pytest`
- Skip Mark
- SkipIf Mark
- Skip Exception
- Xfail Mark (Expected Failure)
"""

import pytest
import unittest


@pytest.mark.skip(reason="some reason")
def test_skip_mark():
assert False


@unittest.expectedFailure
@pytest.mark.skipIf(False, reason="some reason")
def test_skipif_false():
assert False


@pytest.mark.skipIf(True, reason="i don't know")
def test_skipif_true():
assert False


def test_skip_throw():
pytest.skip("it will throw Skipped")


@pytest.mark.xfail
def test_should_fail():
assert False


@pytest.mark.xfail(True)
def test_should_fail_on_condition():
assert False


@pytest.mark.xfail(False)
def test_should_fail_on_condition_ignored():
assert True
20 changes: 20 additions & 0 deletions src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,28 @@ impl PyObject {
unsafe { ffi::PyCode_Check(self.as_ptr()) == 1 }
}

/// Assume is a tuple, and get the size of the tuple
pub fn tuple_size(&self) -> isize {
unsafe { ffi::PyTuple_Size(self.as_ptr()) }
}

/// Assume is a tuple, and get the item at the given index
pub fn get_tuple_item(&self, index: isize) -> PyObject {
let result = unsafe { ffi::PyTuple_GetItem(self.as_ptr(), index) };
let pointer = unsafe { ptr::NonNull::new_unchecked(result) };

PyObject(pointer)
}

/// Assume is a dict, and get the item with the given key
pub fn get_dict_item(&self, key: &CStr) -> Option<PyObject> {
let result = unsafe { ffi::PyDict_GetItemString(self.as_ptr(), key.as_ptr()) };

if result.is_null() {
None
} else {
Some(PyObject::new(result).unwrap())
}
}

/// Assume is a Long, and get the value
Expand All @@ -256,6 +271,11 @@ impl PyObject {
.try_into()
.unwrap()
}

/// Is the object truthy?
pub fn is_truthy(&self) -> bool {
unsafe { ffi::PyObject_IsTrue(self.as_ptr()) == 1 }
}
}
impl fmt::Display for PyObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down
62 changes: 58 additions & 4 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,23 +163,77 @@ fn test_function(test: &Test) -> OutcomeKind {
fn has_skip_annotation(object: &PyObject) -> Option<String> {
const SKIP_ATTRIBUTE: &CStr = c"__unittest_skip__";
const SKIP_REASON_ATTRIBUTE: &CStr = c"__unittest_skip_why__";
const PYTEST_MARK: &CStr = c"pytestmark";

if object.has_truthy_attr(SKIP_ATTRIBUTE) {
let reason = object
.get_attr_cstr(SKIP_REASON_ATTRIBUTE)
.map(|x| x.to_string())
.unwrap_or_default();

Some(reason)
} else {
None
return Some(reason);
}

if object.has_attr(PYTEST_MARK) {
let pytest_marks = object.get_attr_cstr(PYTEST_MARK).unwrap();

for mark in pytest_marks.into_iter() {
let mark_name = mark.get_attr_cstr(c"name").unwrap().to_string();

let should_skip = match mark_name.as_str() {
"skip" => true,
"skipIf" => mark
.get_attr_cstr(c"args")
.is_ok_and(|args| args.get_tuple_item(0).is_truthy()),
_ => false,
};

if should_skip {
if let Ok(kwargs) = mark.get_attr_cstr(c"kwargs") {
return Some(
kwargs
.get_dict_item(c"reason")
.map(|reason| reason.to_string())
.unwrap_or_default(),
);
}
}
}
}

None
}

/// Checks a [`PyObject`] for the annotation for expecting a failure
fn is_expecting_failure(object: &PyObject) -> bool {
const EXPECT_ERROR_ATTRIBUTE: &CStr = c"__unittest_expecting_failure__";
object.has_truthy_attr(EXPECT_ERROR_ATTRIBUTE)
const PYTEST_MARK: &CStr = c"pytestmark";

if object.has_truthy_attr(EXPECT_ERROR_ATTRIBUTE) {
return true;
}

if object.has_attr(PYTEST_MARK) {
let pytest_marks = object.get_attr_cstr(PYTEST_MARK).unwrap();

for mark in pytest_marks.into_iter() {
let mark_name = mark.get_attr_cstr(c"name").unwrap().to_string();

if mark_name == "xfail" {
let args = mark.get_attr_cstr(c"args").unwrap();

if args.tuple_size() > 0 {
let condition = args.get_tuple_item(0);

return condition.is_truthy();
} else {
return true;
};
}
}
}

false
}

fn call_optional_method(object: &PyObject, method: &CStr) -> Result<(), Error> {
Expand Down

0 comments on commit 7a59efe

Please sign in to comment.