Repository: extendr/extendr (rust)

Issue:
Failed R CMD check will not be uploaded
In one of the previous PRs `rcmdcheck` was updated and `check_dir` was set to `"../../../"`, which is actually *outside* of repo's root.
This was done to reference `extendr-*` crates using relative paths.
https://github.com/extendr/extendr/blob/f235216793607e59d7d87877fcf927e08a4bd654/.github/workflows/test.yml#L171

The problem is here
https://github.com/extendr/extendr/blob/f235216793607e59d7d87877fcf927e08a4bd654/.github/workflows/test.yml#L174-L179

If the check fails, artifacts are uploaded from `./check/`, which is no longer produced.
In fact, because check dir is now outside of repo's root, artifacts contaminate the folder outside of extendr's workspace, which is not good. Also, because it is `../../../` and not `../../../check_dir`, artifacts are on the same level as `extendr` repo folder. If I run rcmdcheck on my local machine, then the `../` folder will look like:
```
extendr
extendrtests.Rcmdcheck
extendrtests_0.1.0.tar.gz
```
And I have to remember to delete these artifacts every time.

Pull request:
Dumb rextendr integration tests
`tests/extendrtests` test integration of Rust code into an R package. `{rextendr}` generates rust libraries on the fly and loads them dynamically, using a slightly different method. 
So in #127 it was suggested to test `{rextendr}` as well.

I can suggest two obvious solutions:
1. Write more tests in this repo, testing `{rextendr}` behavior. However, we will have to watch `{rextendr}` changes and update tests to keep up, effectively maintaining two test codebases: here and in `extendr/rextendr`.
2. Use `{rextendr}` itself.

In this PR I suggest using `{rextendr}` as a test framework. We can run `rcmdcheck` on `{rextendr}` and verify its tests pass.
However, there is (yet again) an issue similar to #89 -- `{rextendr}` auto-generated `Cargo.toml` should point to the last commit in the currently tested/merged branch.

I patched `{rextendr}` https://github.com/extendr/rextendr/pull/31, allowing usage of environemnt variables to configure how rust libraries are build, in particular, `REXTENDR_TOOLCHAIN` configures an overriding rust toolchain, and `REXTENDR_PATCH_CRATES_IO` overrides crate references.
So, using `patch.crates_io` it is possible to point `{rextendr}` to the root of the tested extendr repo, effectively fixing #127.

I also found an issue with `extendrtests` and artifact uploading, so this PR also fixes #171 (this is a minor tweak).



Patch:
diff --git a/extendr-api/src/functions.rs b/extendr-api/src/functions.rs
index 8363a0570..8ed122986 100644
--- a/extendr-api/src/functions.rs
+++ b/extendr-api/src/functions.rs
@@ -34,8 +34,7 @@ pub fn new_env_with_capacity(capacity: usize) -> Robj {
         // Unhashed envirnment
         call!("new.env", FALSE, global_env(), 0).unwrap()
     } else {
-        // Hashed environment, the pidgeon hole principle
-        // ensures there are empty slots in the hash table.
+        // Hashed environment for larger hashmaps.
         call!("new.env", TRUE, global_env(), capacity as i32 * 2 + 1).unwrap()
     }
 }
diff --git a/extendr-api/src/iter.rs b/extendr-api/src/iter.rs
index 3606c6153..e9f16db11 100644
--- a/extendr-api/src/iter.rs
+++ b/extendr-api/src/iter.rs
@@ -1,6 +1,24 @@
 use crate::*;
 
-// Iterator over the objects in a VECSXP, EXPRSXP or WEAKREFSXP.
+/// Iterator over the objects in a VECSXP, EXPRSXP or WEAKREFSXP.
+///
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let my_list = list!(a = 1, b = 2);
+///     let mut total = 0;
+///     for robj in my_list.as_list_iter().unwrap() {
+///       if let Some(val) = robj.as_integer() {
+///         total += val;
+///       }
+///     }
+///     assert_eq!(total, 3);
+///    
+///     for name in my_list.names().unwrap() {
+///        assert!(name == "a" || name == "b")
+///     }
+/// }
+/// ```
 #[derive(Clone)]
 pub struct ListIter {
     vector: Robj,
@@ -42,37 +60,95 @@ impl Iterator for ListIter {
     }
 }
 
+/// Generalised iterator of numbers and logical. See Int, Real and Logical.
+pub struct SliceIter<T> {
+    // Control lifetime of vector to make sure the memory is not freed.
+    #[allow(dead_code)]
+    vector: Robj,
+    i: usize,
+    len: usize,
+    ptr: *const T,
+}
+
+impl<T> SliceIter<T> {
+    // A new, empty list iterator.
+    pub fn new() -> Self {
+        SliceIter {
+            vector: ().into(),
+            i: 0,
+            len: 0,
+            ptr: std::ptr::null(),
+        }
+    }
+
+    pub fn from_slice(vector: Robj, slice: &[T]) -> Self {
+        SliceIter {
+            vector,
+            i: 0,
+            len: slice.len(),
+            ptr: slice.as_ptr(),
+        }
+    }
+}
+
+/// Basis of Int, Real and Logical.
+impl<T: Copy> Iterator for SliceIter<T> {
+    type Item = T;
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (self.len, Some(self.len))
+    }
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let i = self.i;
+        self.i += 1;
+        if i >= self.len {
+            self.i = self.len;
+            None
+        } else {
+            unsafe { Some(*self.ptr.offset(i as isize)) }
+        }
+    }
+
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        self.i += n;
+        self.next()
+    }
+}
+
 /// Iterator over name-value pairs in lists.
 pub type NamedListIter = std::iter::Zip<StrIter, ListIter>;
 
 /// Iterator over primitives in integer objects.
-pub type IntegerIter<'a> = std::iter::Cloned<std::slice::Iter<'a, i32>>;
+/// ```
+/// use extendr_api::prelude::*;
+///
+/// fn add(a: Int, b: Int) -> Robj {
+///     a.zip(b).map(|(a, b)| a+b).collect_robj()
+/// }
+/// ```
+pub type Int = SliceIter<i32>;
 
 /// Iterator over primitives in real objects.
-pub type RealIter<'a> = std::iter::Cloned<std::slice::Iter<'a, f64>>;
+/// ```
+/// use extendr_api::prelude::*;
+///
+/// fn add1(a: Real) -> Robj {
+///     a.map(|a| a + 1.0).collect_robj()
+/// }
+/// ```
+pub type Real = SliceIter<f64>;
 
 /// Iterator over primitives in logical objects.
-pub type LogicalIter<'a> = std::iter::Cloned<std::slice::Iter<'a, Bool>>;
-
-/// Iterator over the objects in a vector or string.
-///
 /// ```
 /// use extendr_api::prelude::*;
-/// test! {
-///     let my_list = list!(a = 1, b = 2);
-///     let mut total = 0;
-///     for robj in my_list.as_list_iter().unwrap() {
-///       if let Some(val) = robj.as_integer() {
-///         total += val;
-///       }
-///     }
-///     assert_eq!(total, 3);
-///    
-///     for name in my_list.names().unwrap() {
-///        assert!(name == "a" || name == "b")
-///     }
+///
+/// fn all_true(mut a: Logical) -> bool {
+///     a.all(|a| a.is_true())
 /// }
 /// ```
+pub type Logical = SliceIter<Bool>;
+
 #[derive(Clone)]
 pub struct PairlistIter {
     root_obj: Robj,
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 8f42ef53c..0d07fe4cc 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -41,6 +41,7 @@
 //! test! {
 //!     // An R object with a single string "hello"
 //!     let character = r!("hello");
+//!     let character = r!(["hello", "goodbye"]);
 //!    
 //!     // An R integer object with a single number 1L.
 //!     // Note that in Rust, 1 is an integer and 1.0 is a real.
@@ -52,6 +53,8 @@
 //!    
 //!     // An R real vector.
 //!     let real_vector = r!([1.0, 2.0]);
+//!     let real_vector = &[1.0, 2.0].iter().collect_robj();
+//!     let real_vector = r!(vec![1.0, 2.0]);
 //!    
 //!     // An R function object.
 //!     let function = R!(function(x, y) { x + y })?;
@@ -59,8 +62,16 @@
 //!     // A named list using the list! macro.
 //!     let list = list!(a = 1, b = 2);
 //!    
+//!     // An unnamed list (of R objects) using the List wrapper.
+//!     let list = r!(List(vec![1, 2, 3]));
+//!     let list = r!(List(vec!["a", "b", "c"]));
+//!     let list = r!(List(&[r!("a"), r!(1), r!(2.0)]));
+//!
 //!     // A symbol
 //!     let sym = sym!(wombat);
+//!
+//!     // A R vector using collect_robj()
+//!     let vector = (0..3).map(|x| x * 10).collect_robj();
 //! }
 //! ```
 //!
@@ -484,17 +495,17 @@ mod tests {
     }
 
     #[extendr]
-    pub fn f64_iter(x: RealIter) -> RealIter {
+    pub fn f64_iter(x: Real) -> Real {
         x
     }
 
     #[extendr]
-    pub fn i32_iter(x: IntegerIter) -> IntegerIter {
+    pub fn i32_iter(x: Int) -> Int {
         x
     }
 
     #[extendr]
-    pub fn bool_iter(x: LogicalIter) -> LogicalIter {
+    pub fn bool_iter(x: Logical) -> Logical {
         x
     }
 
@@ -641,19 +652,19 @@ mod tests {
                 assert_eq!(new_owned(wrap__bool_slice(robj.get())), robj);
 
                 // #[extendr]
-                // pub fn f64_iter(x: RealIter) -> RealIter { x }
+                // pub fn f64_iter(x: Real) -> Real { x }
 
                 let robj = r!([1., 2., 3.]);
                 assert_eq!(new_owned(wrap__f64_iter(robj.get())), robj);
 
                 // #[extendr]
-                // pub fn i32_iter(x: IntegerIter) -> IntegerIter { x }
+                // pub fn i32_iter(x: Int) -> Int { x }
 
                 let robj = r!([1, 2, 3]);
                 assert_eq!(new_owned(wrap__i32_iter(robj.get())), robj);
 
                 // #[extendr]
-                // pub fn bool_iter(x: LogicalIter) -> LogicalIter { x }
+                // pub fn bool_iter(x: Logical) -> Logical { x }
 
                 let robj = r!([TRUE, FALSE, TRUE]);
                 assert_eq!(new_owned(wrap__bool_iter(robj.get())), robj);
diff --git a/extendr-api/src/logical.rs b/extendr-api/src/logical.rs
index c8660331d..a87ae26f7 100644
--- a/extendr-api/src/logical.rs
+++ b/extendr-api/src/logical.rs
@@ -16,7 +16,7 @@ impl Bool {
 
     /// Test if TRUE
     pub fn is_true(&self) -> bool {
-        self.0 != 0
+        self.0 == 1
     }
 
     /// Test if FALSE
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index a2b2c4291..e4bc2e65c 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -51,5 +51,5 @@ pub use ndarray::*;
 pub use extendr_macros::{extendr, extendr_module};
 
 pub use super::iter::{
-    EnvIter, IntegerIter, ListIter, LogicalIter, PairlistIter, PairlistTagIter, RealIter, StrIter,
+    EnvIter, Int, ListIter, Logical, PairlistIter, PairlistTagIter, Real, StrIter,
 };
diff --git a/extendr-api/src/robj/from_robj.rs b/extendr-api/src/robj/from_robj.rs
index 9983eb1b1..f00898144 100644
--- a/extendr-api/src/robj/from_robj.rs
+++ b/extendr-api/src/robj/from_robj.rs
@@ -153,9 +153,9 @@ macro_rules! impl_iter_from_robj {
 
 impl_iter_from_robj!(StrIter, as_str_iter, "Not a character vector.");
 impl_iter_from_robj!(ListIter, as_list_iter, "Not a list.");
-impl_iter_from_robj!(IntegerIter<'a>, as_integer_iter, "Not an integer vector.");
-impl_iter_from_robj!(RealIter<'a>, as_real_iter, "Not a real vector.");
-impl_iter_from_robj!(LogicalIter<'a>, as_logical_iter, "Not a logical vector.");
+impl_iter_from_robj!(Int, as_integer_iter, "Not an integer vector.");
+impl_iter_from_robj!(Real, as_real_iter, "Not a real vector.");
+impl_iter_from_robj!(Logical, as_logical_iter, "Not a logical vector.");
 
 /// Pass-through Robj conversion.
 impl<'a> FromRobj<'a> for Robj {
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index 4791a2d52..24221772e 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -497,23 +497,23 @@ impl_from_into_iter! {&'a [T]}
 impl_from_as_iterator! {Range<T>}
 impl_from_as_iterator! {RangeInclusive<T>}
 
-impl<'a> From<RealIter<'a>> for Robj {
+impl From<Real> for Robj {
     /// Convert a real iterator into a vector.
-    fn from(val: RealIter) -> Self {
+    fn from(val: Real) -> Self {
         val.collect_robj()
     }
 }
 
-impl<'a> From<IntegerIter<'a>> for Robj {
+impl From<Int> for Robj {
     /// Convert an integer iterator into a vector.
-    fn from(val: IntegerIter) -> Self {
+    fn from(val: Int) -> Self {
         val.collect_robj()
     }
 }
 
-impl<'a> From<LogicalIter<'a>> for Robj {
+impl From<Logical> for Robj {
     /// Convert a logical iterator into a vector.
-    fn from(val: LogicalIter) -> Self {
+    fn from(val: Logical) -> Self {
         val.collect_robj()
     }
 }
@@ -527,3 +527,10 @@ impl<'a> From<HashMap<&'a str, Robj>> for Robj {
         res
     }
 }
+
+impl<'a> From<Vec<Robj>> for Robj {
+    /// Convert a vector of Robj into a list.
+    fn from(val: Vec<Robj>) -> Self {
+        List(val.iter()).into()
+    }
+}
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 7b4602ff9..5fcdc066b 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -299,12 +299,9 @@ impl Robj {
     /// assert_eq!(tot, 6);
     /// }
     /// ```
-    pub fn as_integer_iter<'a>(&self) -> Option<IntegerIter<'a>>
-    where
-        Self: 'a,
-    {
+    pub fn as_integer_iter(&self) -> Option<Int> {
         if let Some(slice) = self.as_integer_slice() {
-            Some(slice.iter().cloned())
+            Some(Int::from_slice(self.to_owned(), slice))
         } else {
             None
         }
@@ -375,9 +372,9 @@ impl Robj {
     ///     assert_eq!((nt, nf, nna), (1, 1, 1));
     /// }
     /// ```
-    pub fn as_logical_iter(&self) -> Option<LogicalIter> {
+    pub fn as_logical_iter(&self) -> Option<Logical> {
         if let Some(slice) = self.as_logical_slice() {
-            Some(slice.iter().cloned())
+            Some(Logical::from_slice(self.to_owned(), slice))
         } else {
             None
         }
@@ -417,9 +414,9 @@ impl Robj {
     ///     assert_eq!(tot, 6.);
     /// }
     /// ```
-    pub fn as_real_iter(&self) -> Option<RealIter> {
+    pub fn as_real_iter(&self) -> Option<Real> {
         if let Some(slice) = self.as_real_slice() {
-            Some(slice.iter().cloned())
+            Some(Real::from_slice(self.to_owned(), slice))
         } else {
             None
         }
@@ -904,11 +901,7 @@ impl Robj {
     ///    assert_eq!(dim, vec![2, 2]);
     /// }
     /// ```
-    pub fn dim<'a>(&self) -> Option<IntegerIter<'a>>
-    where
-        Self: 'a,
-        Robj: 'a,
-    {
+    pub fn dim(&self) -> Option<Int> {
         if let Some(dim) = self.get_attrib(dim_symbol()) {
             dim.as_integer_iter()
         } else {
diff --git a/extendr-api/src/robj/rinternals.rs b/extendr-api/src/robj/rinternals.rs
index 0c0a7bf08..ba7476608 100644
--- a/extendr-api/src/robj/rinternals.rs
+++ b/extendr-api/src/robj/rinternals.rs
@@ -111,7 +111,9 @@ impl Robj {
     /// test! {
     ///    let my_fun = base_env().find_function(sym!(ls)).unwrap();
     ///    assert_eq!(my_fun.is_function(), true);
-    ///    assert!(base_env().find_function(sym!(qwertyuiop)).is_none());
+    ///
+    ///    // Note: this may crash on some versions of windows which don't support unwinding.
+    ///    // assert!(base_env().find_function(sym!(qwertyuiop)).is_none());
     /// }
     /// ```
     pub fn find_function<K: Into<Robj>>(&self, key: K) -> Option<Robj> {
@@ -157,7 +159,9 @@ impl Robj {
     ///        .find_var(sym!(iris)).unwrap().eval_promise().unwrap();
     ///    assert_eq!(iris_dataframe.is_frame(), true);
     ///    assert_eq!(iris_dataframe.len(), 5);
-    ///    assert_eq!(global_env().find_var(sym!(imnotasymbol)), None);
+    ///
+    ///    // Note: this may crash on some versions of windows which don't support unwinding.
+    ///    //assert_eq!(global_env().find_var(sym!(imnotasymbol)), None);
     /// }
     /// ```
     pub fn find_var<K: Into<Robj>>(&self, key: K) -> Option<Robj> {
diff --git a/extendr-api/src/thread_safety.rs b/extendr-api/src/thread_safety.rs
index 65a0080b3..ded9a44df 100644
--- a/extendr-api/src/thread_safety.rs
+++ b/extendr-api/src/thread_safety.rs
@@ -96,7 +96,7 @@ pub fn throw_r_error<S: AsRef<str>>(s: S) {
 }
 
 /// Wrap an R function such as Rf_findFunction and convert errors and panics into results.
-/// ```
+/// ```ignore
 /// use extendr_api::prelude::*;
 /// test! {
 ///    let res = catch_r_error(|| unsafe {
@@ -138,6 +138,8 @@ where
         let cleandata = std::mem::transmute(&x);
         let cont = R_MakeUnwindCont();
         Rf_protect(cont);
+
+        // Note that catch_unwind does not work for 32 bit windows targets.
         let res = match std::panic::catch_unwind(|| {
             R_UnwindProtect(fun, data, cleanfun, cleandata, cont)
         }) {
diff --git a/extendr-api/src/wrapper.rs b/extendr-api/src/wrapper.rs
index 8c7ff3cbe..3dce4cfdc 100644
--- a/extendr-api/src/wrapper.rs
+++ b/extendr-api/src/wrapper.rs
@@ -1,10 +1,10 @@
 //! Wrappers are lightweight proxies for references to R datatypes.
 //! They do not contain an Robj (see array.rs for an example of this).
 
+#[doc(hidden)]
 use crate::*;
 #[doc(hidden)]
 use libR_sys::*;
-#[doc(hidden)]
 
 /// Wrapper for creating symbols.
 ///
@@ -195,7 +195,7 @@ where
     T::IntoIter: ExactSizeIterator,
     T::Item: Into<Robj>,
 {
-    /// Make a list object from an array of Robjs.
+    /// Make a list object from an iterator of Robjs.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
@@ -214,7 +214,7 @@ where
     T::IntoIter: ExactSizeIterator,
     T::Item: Into<Robj>,
 {
-    /// Make an expression object from an array of Robjs.
+    /// Make an expression object from an iterator of Robjs.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2c2be3cc1..68242a98b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -140,10 +140,23 @@ jobs:
           foreach($target in ($env:BUILD_TARGETS).Split(',')) {
             ci-cargo test $(if($target -ne 'default') {"--target=$target"} ) '--' --nocapture -ActionName "Testing for $target target"
           }
+
+      - name: Obtain 'rextendr'
+        run : |
+          git clone https://github.com/extendr/rextendr ./tests/rextendr
+
+      # Dependencies from both test packages are combined
       - name: Query dependencies for integration testing
         run: |
           install.packages('remotes')
-          saveRDS(remotes::dev_package_deps(pkgdir = "tests/extendrtests", dependencies = TRUE), ".github/depends.Rds", version = 2)
+          saveRDS(
+            unique(
+              rbind(
+                remotes::dev_package_deps(pkgdir = "tests/extendrtests", dependencies = TRUE), 
+                remotes::dev_package_deps(pkgdir = "tests/rextendr", dependencies = TRUE))),
+            ".github/depends.Rds", 
+            version = 2
+          )
           writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
         shell: Rscript {0}
 
@@ -154,29 +167,97 @@ jobs:
           key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
           restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-
 
-      - name: Install R dependencies for integration testing
+      # Currently rextendr requires R > 4, so if R version is older (e.g. 3.6.3), rextendr tests are omitted
+      # Regex is used to inject absolute path into Cargo.toml
+      - name: Install dependencies & configure R for integration testing
         run: |
           remotes::install_deps(pkgdir = "tests/extendrtests", dependencies = TRUE)
+          if (as.integer(R.version$major) >= 4) { 
+            remotes::install_deps(pkgdir = "tests/rextendr", dependencies = TRUE)
+          }
           remotes::install_cran("rcmdcheck")
+
+          api_path <- normalizePath(file.path(getwd(), "extendr-api"), winslash = "/")
+          toml_path <- file.path(getwd(), "tests", "extendrtests", "src", "rust", "Cargo.toml")
+          lines <- readLines(toml_path)
+          lines <- gsub(
+            "(^\\s*extendr-api\\s*=\\s*\\{\\s*path\\s*=\\s*\")(.*?)(\"\\s*\\})",
+            paste0("\\1", api_path, "\\3"),
+            lines
+          )
+          writeLines(lines, toml_path)
         shell: Rscript {0}
         
       # Windows: modified PATH will launch 32-bit Rscript if the build targets only i686/i386, so no conflict will arise
       # Rust pseudo multi-targeting: R CMD CHECK receives an extra argument '--no-multiarch' if BUILD_TARGETS is not 'default' and does not contain any commas
-      - name: Run R integration tests
-        id: r_integration_tests
+      # To enable RStudio support, extendrtests should be installable from 'extendr/tests/extendrtests',
+      # for this work directory should be modified and then reverted back
+      - name: Run R integration tests using {extendrtests}
+        id: extendrtests_check
         env:
           _R_CHECK_CRAN_INCOMING_REMOTE_: false
         run: |
-          setwd("tests/extendrtests")
-          rcmdcheck::rcmdcheck(path = ".", args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "../../../")
+          cat("::group::Checking 'extendrtests'\n")
+          rcmdcheck::rcmdcheck(
+            path = "tests/extendrtests", 
+            args = c("--no-manual", "--as-cran", "--force-multiarch"),
+            error_on = "warning", 
+            check_dir = "extendrtests_check"
+          )
+          cat("::endgroup::\n")
         shell: Rscript {0}
         
-      - name: Upload check results from R integration tests
-        if: failure()
+      # If R is older than 4.0, rextendr is not tested and a warning is emitted. 
+      # With https://github.com/extendr/rextendr/pull/31
+      # rextendr can be configured using environment variables.
+      # 'patch.crates_io' is used to point libraries to local copies of
+      # extendr-api and extendr-macros, so rextendr tests current version of extendr-*
+      - name: Run R integration tests using {rextendr}
+        id: rextendr_check
+        env:
+          _R_CHECK_CRAN_INCOMING_REMOTE_: false
+        run: |
+          if (as.integer(R.version$major) < 4) {
+            cat("::warning:: R version is not compatible with 'rextendr'\n")
+          } else {
+            cat("::group::Checking 'rextendr'\n")
+            
+            patch.crates_io <-
+              paste(
+                paste0(
+                  "extendr-api = { path = \"",
+                  normalizePath(file.path(getwd(), "extendr-api"), winslash = "/"),
+                  "\" }"),
+                paste0(
+                  "extendr-macros = { path = \"",
+                  normalizePath(file.path(getwd(), "extendr-macros"), winslash = "/"),
+                  "\" }"),
+                sep = ";")
+
+            Sys.setenv(REXTENDR_PATCH_CRATES_IO = patch.crates_io)
+            rcmdcheck::rcmdcheck(
+              path = "tests/rextendr", 
+              args = c("--no-manual", "--as-cran", "--force-multiarch"), 
+              error_on = "warning", 
+              check_dir = "rextendr_check")
+            cat("::endgroup::\n")
+          }
+        shell: Rscript {0}
+
+
+      - name: Upload extendrtests check results from R integration tests
+        if: steps.extendrtests_check.outcome != 'success'
+        uses: actions/upload-artifact@main
+        with:
+          name:  extendrtests-${{ matrix.config.os }}-R-${{ matrix.config.r }}-rust-${{ matrix.config.rust-version }}
+          path: extendrtests_check
+
+      - name: Upload rextendr check results from R integration tests
+        if: steps.rextendr_check.outcome != 'success'
         uses: actions/upload-artifact@main
         with:
-          name:  ${{ matrix.config.os }}-R-${{ matrix.config.r }}-rust-${{ matrix.config.rust-version }}
-          path: check
+          name:  rextendr-${{ matrix.config.os }}-R-${{ matrix.config.r }}-rust-${{ matrix.config.rust-version }}
+          path: rextendr_check
 
 
   # All tests under this job are run with R devel and freshly generated bindings.
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index b2872283d..de75734ab 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -227,7 +227,7 @@ fn output_iterator_test() {
 // Test that we can use Iterators as the input to functions.
 // eg.
 // #[extendr]
-// fn fred(a: RealIter, b: RealIter) -> RealIter {
+// fn fred(a: Real, b: Real) -> Robj {
 // }
 #[test]
 fn input_iterator_test() {
@@ -244,18 +244,18 @@ fn input_iterator_test() {
 
         let src: &[i32] = &[1, 2, 3];
         let robj = Robj::from(src);
-        let iter = <IntegerIter>::from_robj(&robj).unwrap();
+        let iter = <Int>::from_robj(&robj).unwrap();
         assert_eq!(iter.collect::<Vec<_>>(), src);
 
         let src: &[f64] = &[1., 2., 3.];
         let robj = Robj::from(src);
-        let iter = <RealIter>::from_robj(&robj).unwrap();
+        let iter = <Real>::from_robj(&robj).unwrap();
         assert_eq!(iter.collect::<Vec<_>>(), src);
 
         /*
         let src: &[Bool] = &[TRUE, FALSE, TRUE];
         let robj = Robj::from(src);
-        let iter = <LogicalIter>::from_robj(&robj).unwrap();
+        let iter = <Logical>::from_robj(&robj).unwrap();
         assert_eq!(iter.collect::<Vec<_>>(), src);
         */
     }
diff --git a/tests/extendrtests/src/rust/Cargo.toml b/tests/extendrtests/src/rust/Cargo.toml
index 035e26cb3..562b44c59 100644
--- a/tests/extendrtests/src/rust/Cargo.toml
+++ b/tests/extendrtests/src/rust/Cargo.toml
@@ -16,10 +16,10 @@ crate-type = ["staticlib"]
 extendr-api = "*"
 
 [patch.crates-io]
-## Build against local extendr sources. Check locally by 
-## running rcmdcheck::rcmdcheck(check_dir = "../../../") from
-## the root of extendrtests.
-extendr-api = { path = "../../../../../extendr/extendr-api/"}
+## This is configured to work with RStudio features.
+## Replace by absolute path to simplify testing.
+## CI overrides this path.
+extendr-api = { path = "../../../../../extendr/extendr-api"}
 
 ## Build against current extendr version on github. Not recommended
 ## for development work.