diff mbox series

[V4,7/8] libgpiod: Add rust tests

Message ID 78b3ee21dec2a66003c2eae2800e9699a8ecd180.1657279685.git.viresh.kumar@linaro.org
State New
Headers show
Series libgpiod: Add Rust bindings | expand

Commit Message

Viresh Kumar July 8, 2022, 11:35 a.m. UTC
Add tests for the rust bindings, quite similar to the ones in cxx
bindings.

This is how "cargo test" prints results:

     Running tests/chip.rs (target/debug/deps/chip-b19008e6b9a10d2f)

running 6 tests
test chip::create::nonexistent_file_failure ... ok
test chip::create::no_dev_file_failure ... ok
test chip::create::non_gpio_char_dev_file_failure ... ok
test chip::create::existing ... ok
test chip::configure::line_lookup ... ok
test chip::configure::verify ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s

     Running tests/edge_event.rs (target/debug/deps/edge_event-5897a36c91efe0d4)

running 11 tests
test edge_event::buffer_settings::default_capacity ... ok
test edge_event::buffer_settings::user_defined_capacity ... ok
test edge_event::buffer_settings::max_capacity ... ok
test edge_event::failure::dir_out_edge_failure ... ok
test edge_event::failure::wait_timeout ... ok
test edge_event::verify::both_edges ... ok
test edge_event::verify::multiple_events ... ok
test edge_event::verify::edge_sequence ... ok
test edge_event::verify::falling_edge ... ok
test edge_event::verify::over_capacity ... ok
test edge_event::verify::rising_edge ... ok

test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.98s

     Running tests/info_event.rs (target/debug/deps/info_event-707c734e0820381c)

running 3 tests
test info_event::watch::verify ... ok
test info_event::watch::failure ... ok
test info_event::watch::reconfigure ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s

     Running tests/line_config.rs (target/debug/deps/line_config-d376966750bf38a0)

running 9 tests
test line_config::overrides::active_low ... ok
test line_config::overrides::bias ... ok
test line_config::default::verify ... ok
test line_config::overrides::debounce_period ... ok
test line_config::overrides::direction ... ok
test line_config::overrides::drive ... ok
test line_config::overrides::edge_detection ... ok
test line_config::overrides::event_clock ... ok
test line_config::overrides::output_value ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

     Running tests/line_info.rs (target/debug/deps/line_info-5069196cfaa74ab7)

running 2 tests
[40570.967204] gpio-1992 (hog): hogged as output/high
[40570.974774] gpio-1956 (hog4): hogged as output/low
[40570.975314] gpio-1955 (hog3): hogged as output/high
test line_info::properties::verify ... ok
test line_info::basic::verify ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s

     Running tests/line_request.rs (target/debug/deps/line_request-914b9fd8c84e2c4b)

running 7 tests
test line_request::invalid_arguments::no_offsets ... ok
test line_request::verify::read_values ... ok
test line_request::verify::set_bias ... ok
test line_request::verify::reconfigure_output_values ... ok
test line_request::verify::empty_consumer ... ok
test line_request::verify::set_output_values ... ok
test line_request::verify::custom_consumer ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s

     Running tests/request_config.rs (target/debug/deps/request_config-99e366517cc0feda)

running 2 tests
test request_config::verify::default ... ok
test request_config::verify::initialized ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

   Doc-tests libgpiod

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
---
 bindings/rust/Cargo.toml              |   3 +
 bindings/rust/src/line_request.rs     |   1 +
 bindings/rust/tests/chip.rs           |  96 +++++++
 bindings/rust/tests/common/config.rs  | 117 ++++++++
 bindings/rust/tests/common/mod.rs     |  16 ++
 bindings/rust/tests/common/sim.rs     | 306 ++++++++++++++++++++
 bindings/rust/tests/edge_event.rs     | 389 ++++++++++++++++++++++++++
 bindings/rust/tests/info_event.rs     | 126 +++++++++
 bindings/rust/tests/line_config.rs    | 187 +++++++++++++
 bindings/rust/tests/line_info.rs      |  90 ++++++
 bindings/rust/tests/line_request.rs   | 234 ++++++++++++++++
 bindings/rust/tests/request_config.rs |  42 +++
 12 files changed, 1607 insertions(+)
 create mode 100644 bindings/rust/tests/chip.rs
 create mode 100644 bindings/rust/tests/common/config.rs
 create mode 100644 bindings/rust/tests/common/mod.rs
 create mode 100644 bindings/rust/tests/common/sim.rs
 create mode 100644 bindings/rust/tests/edge_event.rs
 create mode 100644 bindings/rust/tests/info_event.rs
 create mode 100644 bindings/rust/tests/line_config.rs
 create mode 100644 bindings/rust/tests/line_info.rs
 create mode 100644 bindings/rust/tests/line_request.rs
 create mode 100644 bindings/rust/tests/request_config.rs

Comments

Kent Gibson July 27, 2022, 2:58 a.m. UTC | #1
On Fri, Jul 08, 2022 at 05:05:00PM +0530, Viresh Kumar wrote:
> Add tests for the rust bindings, quite similar to the ones in cxx
> bindings.
> 
> This is how "cargo test" prints results:
> 

Don't include the test results in the commit message.
Those are more of a release artifact than a commit artifact.
It is assumed the tests pass for you or you wouldn't be submitting them.

>      Running tests/chip.rs (target/debug/deps/chip-b19008e6b9a10d2f)
> 
> running 6 tests
> test chip::create::nonexistent_file_failure ... ok
> test chip::create::no_dev_file_failure ... ok
> test chip::create::non_gpio_char_dev_file_failure ... ok
> test chip::create::existing ... ok
> test chip::configure::line_lookup ... ok
> test chip::configure::verify ... ok
> 
> test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
> 
>      Running tests/edge_event.rs (target/debug/deps/edge_event-5897a36c91efe0d4)
> 
> running 11 tests
> test edge_event::buffer_settings::default_capacity ... ok
> test edge_event::buffer_settings::user_defined_capacity ... ok
> test edge_event::buffer_settings::max_capacity ... ok
> test edge_event::failure::dir_out_edge_failure ... ok
> test edge_event::failure::wait_timeout ... ok
> test edge_event::verify::both_edges ... ok
> test edge_event::verify::multiple_events ... ok
> test edge_event::verify::edge_sequence ... ok
> test edge_event::verify::falling_edge ... ok
> test edge_event::verify::over_capacity ... ok
> test edge_event::verify::rising_edge ... ok
> 
> test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.98s
> 
>      Running tests/info_event.rs (target/debug/deps/info_event-707c734e0820381c)
> 
> running 3 tests
> test info_event::watch::verify ... ok
> test info_event::watch::failure ... ok
> test info_event::watch::reconfigure ... ok
> 
> test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s
> 
>      Running tests/line_config.rs (target/debug/deps/line_config-d376966750bf38a0)
> 
> running 9 tests
> test line_config::overrides::active_low ... ok
> test line_config::overrides::bias ... ok
> test line_config::default::verify ... ok
> test line_config::overrides::debounce_period ... ok
> test line_config::overrides::direction ... ok
> test line_config::overrides::drive ... ok
> test line_config::overrides::edge_detection ... ok
> test line_config::overrides::event_clock ... ok
> test line_config::overrides::output_value ... ok
> 
> test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
> 
>      Running tests/line_info.rs (target/debug/deps/line_info-5069196cfaa74ab7)
> 
> running 2 tests
> [40570.967204] gpio-1992 (hog): hogged as output/high
> [40570.974774] gpio-1956 (hog4): hogged as output/low
> [40570.975314] gpio-1955 (hog3): hogged as output/high
> test line_info::properties::verify ... ok
> test line_info::basic::verify ... ok
> 
> test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s
> 
>      Running tests/line_request.rs (target/debug/deps/line_request-914b9fd8c84e2c4b)
> 
> running 7 tests
> test line_request::invalid_arguments::no_offsets ... ok
> test line_request::verify::read_values ... ok
> test line_request::verify::set_bias ... ok
> test line_request::verify::reconfigure_output_values ... ok
> test line_request::verify::empty_consumer ... ok
> test line_request::verify::set_output_values ... ok
> test line_request::verify::custom_consumer ... ok
> 
> test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s
> 
>      Running tests/request_config.rs (target/debug/deps/request_config-99e366517cc0feda)
> 
> running 2 tests
> test request_config::verify::default ... ok
> test request_config::verify::initialized ... ok
> 
> test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
> 
>    Doc-tests libgpiod
> 
> running 0 tests
> 
> test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
> 
> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
> ---
>  bindings/rust/Cargo.toml              |   3 +
>  bindings/rust/src/line_request.rs     |   1 +
>  bindings/rust/tests/chip.rs           |  96 +++++++
>  bindings/rust/tests/common/config.rs  | 117 ++++++++
>  bindings/rust/tests/common/mod.rs     |  16 ++
>  bindings/rust/tests/common/sim.rs     | 306 ++++++++++++++++++++
>  bindings/rust/tests/edge_event.rs     | 389 ++++++++++++++++++++++++++
>  bindings/rust/tests/info_event.rs     | 126 +++++++++
>  bindings/rust/tests/line_config.rs    | 187 +++++++++++++
>  bindings/rust/tests/line_info.rs      |  90 ++++++
>  bindings/rust/tests/line_request.rs   | 234 ++++++++++++++++
>  bindings/rust/tests/request_config.rs |  42 +++
>  12 files changed, 1607 insertions(+)
>  create mode 100644 bindings/rust/tests/chip.rs
>  create mode 100644 bindings/rust/tests/common/config.rs
>  create mode 100644 bindings/rust/tests/common/mod.rs
>  create mode 100644 bindings/rust/tests/common/sim.rs
>  create mode 100644 bindings/rust/tests/edge_event.rs
>  create mode 100644 bindings/rust/tests/info_event.rs
>  create mode 100644 bindings/rust/tests/line_config.rs
>  create mode 100644 bindings/rust/tests/line_info.rs
>  create mode 100644 bindings/rust/tests/line_request.rs
>  create mode 100644 bindings/rust/tests/request_config.rs
> 
> diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml
> index d5d81486fa2f..e2d6d5bb91b6 100644
> --- a/bindings/rust/Cargo.toml
> +++ b/bindings/rust/Cargo.toml
> @@ -10,3 +10,6 @@ libc = ">=0.2.39"
>  libgpiod-sys = { path = "libgpiod-sys" }
>  thiserror = "1.0"
>  vmm-sys-util = "=0.9.0"
> +
> +[dev-dependencies]
> +libgpiod-sys = { path = "libgpiod-sys", features = ["gpiosim"] }
> diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs
> index bb338e72671d..c1dbbb397e73 100644
> --- a/bindings/rust/src/line_request.rs
> +++ b/bindings/rust/src/line_request.rs
> @@ -15,6 +15,7 @@ use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestC
>  /// Line request operations
>  ///
>  /// Allows interaction with a set of requested lines.
> +#[derive(Debug)]
>  pub struct LineRequest {
>      request: *mut bindings::gpiod_line_request,
>  }

Overlooked in patch 6?  Squash that into patch 4 as well.

> diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs
> new file mode 100644
> index 000000000000..4e64e9c7e291
> --- /dev/null
> +++ b/bindings/rust/tests/chip.rs
> @@ -0,0 +1,96 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod chip {
> +    use libc::{ENODEV, ENOENT, ENOTTY};
> +
> +    use vmm_sys_util::errno::Error as IoError;
> +
> +    use crate::common::*;
> +    use libgpiod::{Chip, Error as ChipError};
> +
> +    mod create {

If it is testing the open method then call it "open"?

> +        use super::*;
> +
> +        #[test]
> +        fn nonexistent_file_failure() {
> +            assert_eq!(
> +                Chip::open("/dev/nonexistent").unwrap_err(),
> +                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOENT))
> +            );
> +        }

The "_failure" suffix doesn't add anything, so remove it.

> +
> +        #[test]
> +        fn no_dev_file_failure() {
> +            assert_eq!(
> +                Chip::open("/tmp").unwrap_err(),
> +                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOTTY))
> +            );
> +        }
> +
> +        #[test]
> +        fn non_gpio_char_dev_file_failure() {
> +            assert_eq!(
> +                Chip::open("/dev/null").unwrap_err(),
> +                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENODEV))
> +            );
> +        }
> +
> +        #[test]
> +        fn existing() {
> +            let sim = Sim::new(None, None, true).unwrap();
> +            Chip::open(sim.dev_path()).unwrap();

Test names should indicate what the test covers. "existing" doesn't
quite do that for me, though last I checked I passed.  YMMV.

Use an assert variant for explicit tests.
Only use unwrap() for intermediate steps that are part of the fixture and
are expected to pass unless something is very broken in the test setup.
That makes it easier to distinguish the meat from the skeleton.

> +        }
> +    }
> +
> +    mod configure {
> +        use super::*;
> +        const NGPIO: u64 = 16;
> +        const LABEL: &str = "foobar";
> +
> +        #[test]
> +        fn verify() {
> +            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
> +            let chip = Chip::open(sim.dev_path()).unwrap();
> +
> +            assert_eq!(chip.get_label().unwrap(), LABEL);
> +            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
> +            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
> +            assert_eq!(chip.get_num_lines(), NGPIO as u32);
> +            chip.get_fd().unwrap();
> +        }
> +

A test for a basic open on an existing chip and a smoke test of some
chip methods is called "verify", so chip::configure::verify?

> +        #[test]
> +        fn line_lookup() {
> +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> +            sim.set_line_name(0, "zero").unwrap();
> +            sim.set_line_name(2, "two").unwrap();
> +            sim.set_line_name(3, "three").unwrap();
> +            sim.set_line_name(5, "five").unwrap();
> +            sim.set_line_name(10, "ten").unwrap();
> +            sim.set_line_name(11, "ten").unwrap();
> +            sim.enable().unwrap();
> +
> +            let chip = Chip::open(sim.dev_path()).unwrap();
> +
> +            // Success case
> +            assert_eq!(chip.find_line("zero").unwrap(), 0);
> +            assert_eq!(chip.find_line("two").unwrap(), 2);
> +            assert_eq!(chip.find_line("three").unwrap(), 3);
> +            assert_eq!(chip.find_line("five").unwrap(), 5);
> +
> +            // Success with duplicate names, should return first entry
> +            assert_eq!(chip.find_line("ten").unwrap(), 10);
> +
> +            // Failure
> +            assert_eq!(
> +                chip.find_line("nonexistent").unwrap_err(),
> +                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
> +            );
> +        }

If it is testing find_line() then why not call it find_line?

> +    }
> +}
> diff --git a/bindings/rust/tests/common/config.rs b/bindings/rust/tests/common/config.rs
> new file mode 100644
> index 000000000000..3abd9a8c4c8b
> --- /dev/null
> +++ b/bindings/rust/tests/common/config.rs
> @@ -0,0 +1,117 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +use std::sync::Arc;
> +
> +use crate::common::*;
> +
> +use libgpiod::{Bias, Chip, Direction, Edge, LineConfig, LineRequest, RequestConfig, Result};
> +
> +//#[derive(Debug)]

Uncomment or remove.

> +pub(crate) struct TestConfig {
> +    sim: Arc<Sim>,
> +    chip: Option<Chip>,
> +    request: Option<LineRequest>,
> +    rconfig: RequestConfig,
> +    lconfig: LineConfig,
> +}
> +
> +impl TestConfig {
> +    pub(crate) fn new(ngpio: u64) -> Result<Self> {
> +        Ok(Self {
> +            sim: Arc::new(Sim::new(Some(ngpio), None, true)?),
> +            chip: None,
> +            request: None,
> +            rconfig: RequestConfig::new().unwrap(),
> +            lconfig: LineConfig::new().unwrap(),
> +        })
> +    }
> +
> +    pub(crate) fn set_pull(&self, offsets: &[u32], pulls: &[u32]) {
> +        for i in 0..pulls.len() {
> +            self.sim.set_pull(offsets[i], pulls[i] as i32).unwrap();
> +        }
> +    }
> +
> +    pub(crate) fn rconfig_consumer(&self, offsets: Option<&[u32]>, consumer: Option<&str>) {
> +        if let Some(offsets) = offsets {
> +            self.rconfig.set_offsets(offsets);
> +        }
> +
> +        if let Some(consumer) = consumer {
> +            self.rconfig.set_consumer(consumer);
> +        }
> +    }
> +
> +    pub(crate) fn rconfig(&self, offsets: Option<&[u32]>) {
> +        self.rconfig_consumer(offsets, None);
> +    }

Almost always called with Some(&offsets), and calling it with None is a
NOP.
Where called with Some just call rconfig.set_offsets().
Remove instances of calling with None.

> +
> +    pub(crate) fn lconfig(
> +        &mut self,
> +        dir: Option<Direction>,
> +        val: Option<u32>,
> +        val_override: Option<(u32, u32)>,
> +        edge: Option<Edge>,
> +        bias: Option<Bias>,
> +    ) {
> +        if let Some(bias) = bias {
> +            self.lconfig.set_bias_default(bias);
> +        }
> +
> +        if let Some(edge) = edge {
> +            self.lconfig.set_edge_detection_default(edge);
> +        }
> +
> +        if let Some(dir) = dir {
> +            self.lconfig.set_direction_default(dir);
> +        }
> +
> +        if let Some(val) = val {
> +            self.lconfig.set_output_value_default(val);
> +        }
> +
> +        if let Some((offset, val)) = val_override {
> +            self.lconfig.set_output_value_override(val, offset);
> +        }
> +    }
> +
> +    pub(crate) fn lconfig_raw(&mut self) {
> +        self.lconfig(None, None, None, None, None);
> +    }
> +
> +    pub(crate) fn lconfig_edge(&mut self, edge: Option<Edge>) {
> +        self.lconfig(None, None, None, edge, None);
> +    }
> +
> +    pub(crate) fn request_lines(&mut self) -> Result<()> {
> +        let chip = Chip::open(self.sim.dev_path())?;
> +
> +        self.request = Some(chip.request_lines(&self.rconfig, &self.lconfig)?);
> +        self.chip = Some(chip);
> +
> +        Ok(())
> +    }
> +
> +    pub(crate) fn sim(&self) -> Arc<Sim> {
> +        self.sim.clone()
> +    }
> +
> +    pub(crate) fn chip(&self) -> &Chip {
> +        &self.chip.as_ref().unwrap()
> +    }
> +
> +    pub(crate) fn request(&self) -> &LineRequest {
> +        &self.request.as_ref().unwrap()
> +    }
> +}
> +
> +impl Drop for TestConfig {
> +    fn drop(&mut self) {
> +        // Explicit freeing is important to make sure "request" get freed
> +        // before "sim" and "chip".
> +        self.request = None;
> +    }
> +}
> diff --git a/bindings/rust/tests/common/mod.rs b/bindings/rust/tests/common/mod.rs
> new file mode 100644
> index 000000000000..2dc37986396b
> --- /dev/null
> +++ b/bindings/rust/tests/common/mod.rs
> @@ -0,0 +1,16 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +#[allow(dead_code)]
> +mod sim;
> +
> +#[allow(unused_imports)]
> +pub(crate) use sim::*;

This allow seems unnecessary.

> +
> +#[allow(dead_code)]
> +mod config;
> +
> +#[allow(unused_imports)]
> +pub(crate) use config::*;

The dead_code allows make me think there is a problem with the way the
common test code is structured or there is actual dead code in there.

common/sim.rs should be part of a gpiosim crate??

> diff --git a/bindings/rust/tests/common/sim.rs b/bindings/rust/tests/common/sim.rs
> new file mode 100644
> index 000000000000..cd5ec66c3da5
> --- /dev/null
> +++ b/bindings/rust/tests/common/sim.rs
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +use std::os::raw::c_char;
> +use std::{slice, str};
> +
> +use vmm_sys_util::errno::Error as IoError;
> +
> +use libgpiod::{Error, Result};
> +use libgpiod_sys as bindings;
> +
> +/// Sim Ctx
> +#[derive(Debug)]
> +struct SimCtx {
> +    ctx: *mut bindings::gpiosim_ctx,
> +}
> +
> +unsafe impl Send for SimCtx {}
> +unsafe impl Sync for SimCtx {}
> +
> +impl SimCtx {
> +    fn new() -> Result<Self> {
> +        let ctx = unsafe { bindings::gpiosim_ctx_new() };
> +        if ctx.is_null() {
> +            return Err(Error::OperationFailed("gpio-sim ctx new", IoError::last()));
> +        }
> +
> +        Ok(Self { ctx })
> +    }
> +
> +    fn ctx(&self) -> *mut bindings::gpiosim_ctx {
> +        self.ctx
> +    }
> +}
> +
> +impl Drop for SimCtx {
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiosim_ctx_unref(self.ctx) }
> +    }
> +}
> +
> +/// Sim Dev
> +#[derive(Debug)]
> +struct SimDev {
> +    dev: *mut bindings::gpiosim_dev,
> +}
> +
> +unsafe impl Send for SimDev {}
> +unsafe impl Sync for SimDev {}
> +
> +impl SimDev {
> +    fn new(ctx: &SimCtx) -> Result<Self> {
> +        let dev = unsafe { bindings::gpiosim_dev_new(ctx.ctx()) };
> +        if dev.is_null() {
> +            return Err(Error::OperationFailed("gpio-sim dev new", IoError::last()));
> +        }
> +
> +        Ok(Self { dev })
> +    }
> +
> +    fn dev(&self) -> *mut bindings::gpiosim_dev {
> +        self.dev
> +    }
> +
> +    fn enable(&self) -> Result<()> {
> +        let ret = unsafe { bindings::gpiosim_dev_enable(self.dev) };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "gpio-sim dev-enable",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +
> +    fn disable(&self) -> Result<()> {
> +        let ret = unsafe { bindings::gpiosim_dev_disable(self.dev) };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "gpio-sim dev-disable",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +}
> +
> +impl Drop for SimDev {
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiosim_dev_unref(self.dev) }
> +    }
> +}
> +
> +/// Sim Bank
> +#[derive(Debug)]
> +struct SimBank {
> +    bank: *mut bindings::gpiosim_bank,
> +}
> +
> +unsafe impl Send for SimBank {}
> +unsafe impl Sync for SimBank {}
> +
> +impl SimBank {
> +    fn new(dev: &SimDev) -> Result<Self> {
> +        let bank = unsafe { bindings::gpiosim_bank_new(dev.dev()) };
> +        if bank.is_null() {
> +            return Err(Error::OperationFailed("gpio-sim Bank new", IoError::last()));
> +        }
> +
> +        Ok(Self { bank })
> +    }
> +
> +    fn chip_name(&self) -> Result<&str> {
> +        // SAFETY: The string returned by gpiosim is guaranteed to live as long
> +        // as the `struct SimBank`.
> +        let name = unsafe { bindings::gpiosim_bank_get_chip_name(self.bank) };
> +
> +        // SAFETY: The string is guaranteed to be valid here.
> +        str::from_utf8(unsafe {
> +            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
> +        })
> +        .map_err(Error::InvalidString)
> +    }
> +
> +    fn dev_path(&self) -> Result<&str> {
> +        // SAFETY: The string returned by gpiosim is guaranteed to live as long
> +        // as the `struct SimBank`.
> +        let path = unsafe { bindings::gpiosim_bank_get_dev_path(self.bank) };
> +
> +        // SAFETY: The string is guaranteed to be valid here.
> +        str::from_utf8(unsafe {
> +            slice::from_raw_parts(path as *const u8, bindings::strlen(path) as usize)
> +        })
> +        .map_err(Error::InvalidString)
> +    }
> +
> +    fn val(&self, offset: u32) -> Result<u32> {
> +        let ret = unsafe { bindings::gpiosim_bank_get_value(self.bank, offset) };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "gpio-sim get-value",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(ret as u32)
> +        }
> +    }
> +
> +    fn set_label(&self, label: &str) -> Result<()> {
> +        // Null-terminate the string
> +        let label = label.to_owned() + "\0";
> +
> +        let ret =
> +            unsafe { bindings::gpiosim_bank_set_label(self.bank, label.as_ptr() as *const c_char) };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "gpio-sim set-label",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +
> +    fn set_num_lines(&self, num: u64) -> Result<()> {
> +        let ret = unsafe { bindings::gpiosim_bank_set_num_lines(self.bank, num) };
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "gpio-sim set-num-lines",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +
> +    fn set_line_name(&self, offset: u32, name: &str) -> Result<()> {
> +        // Null-terminate the string
> +        let name = name.to_owned() + "\0";
> +
> +        let ret = unsafe {
> +            bindings::gpiosim_bank_set_line_name(self.bank, offset, name.as_ptr() as *const c_char)
> +        };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "gpio-sim set-line-name",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +
> +    fn set_pull(&self, offset: u32, pull: i32) -> Result<()> {
> +        let ret = unsafe { bindings::gpiosim_bank_set_pull(self.bank, offset, pull) };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed("gpio-sim set-pull", IoError::last()))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +
> +    fn hog_line(&self, offset: u32, name: &str, dir: i32) -> Result<()> {
> +        // Null-terminate the string
> +        let name = name.to_owned() + "\0";
> +
> +        let ret = unsafe {
> +            bindings::gpiosim_bank_hog_line(self.bank, offset, name.as_ptr() as *const c_char, dir)
> +        };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed("gpio-sim hog-line", IoError::last()))
> +        } else {
> +            Ok(())
> +        }
> +    }
> +}
> +
> +impl Drop for SimBank {
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiosim_bank_unref(self.bank) }
> +    }
> +}
> +
> +/// GPIO SIM
> +#[derive(Debug)]
> +pub(crate) struct Sim {
> +    ctx: SimCtx,
> +    dev: SimDev,
> +    bank: SimBank,
> +}
> +
> +unsafe impl Send for Sim {}
> +unsafe impl Sync for Sim {}
> +
> +impl Sim {
> +    pub(crate) fn new(ngpio: Option<u64>, label: Option<&str>, enable: bool) -> Result<Self> {
> +        let ctx = SimCtx::new()?;
> +        let dev = SimDev::new(&ctx)?;
> +        let bank = SimBank::new(&dev)?;
> +
> +        if let Some(ngpio) = ngpio {
> +            bank.set_num_lines(ngpio)?;
> +        }
> +
> +        if let Some(label) = label {
> +            bank.set_label(label)?;
> +        }
> +
> +        if enable {
> +            dev.enable()?;
> +        }
> +
> +        Ok(Self { ctx, dev, bank })
> +    }
> +
> +    pub(crate) fn chip_name(&self) -> &str {
> +        self.bank.chip_name().unwrap()
> +    }
> +
> +    pub fn dev_path(&self) -> &str {
> +        self.bank.dev_path().unwrap()
> +    }
> +
> +    pub(crate) fn val(&self, offset: u32) -> Result<u32> {
> +        self.bank.val(offset)
> +    }
> +
> +    pub(crate) fn set_label(&self, label: &str) -> Result<()> {
> +        self.bank.set_label(label)
> +    }
> +
> +    pub(crate) fn set_num_lines(&self, num: u64) -> Result<()> {
> +        self.bank.set_num_lines(num)
> +    }
> +
> +    pub(crate) fn set_line_name(&self, offset: u32, name: &str) -> Result<()> {
> +        self.bank.set_line_name(offset, name)
> +    }
> +
> +    pub(crate) fn set_pull(&self, offset: u32, pull: i32) -> Result<()> {
> +        self.bank.set_pull(offset, pull)
> +    }
> +
> +    pub(crate) fn hog_line(&self, offset: u32, name: &str, dir: i32) -> Result<()> {
> +        self.bank.hog_line(offset, name, dir)
> +    }
> +
> +    pub(crate) fn enable(&self) -> Result<()> {
> +        self.dev.enable()
> +    }
> +
> +    pub(crate) fn disable(&self) -> Result<()> {
> +        self.dev.disable()
> +    }
> +}
> diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs
> new file mode 100644
> index 000000000000..1b05b225aab7
> --- /dev/null
> +++ b/bindings/rust/tests/edge_event.rs
> @@ -0,0 +1,389 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod edge_event {
> +    use libc::EINVAL;
> +    use std::sync::Arc;
> +    use std::thread::{sleep, spawn};
> +    use std::time::Duration;
> +
> +    use vmm_sys_util::errno::Error as IoError;
> +
> +    use crate::common::*;
> +    use libgpiod::{Direction, Edge, EdgeEventBuffer, Error as ChipError, LineEdgeEvent};
> +    use libgpiod_sys::{GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP};
> +
> +    const NGPIO: u64 = 8;
> +
> +    mod buffer_settings {
> +        use super::*;
> +
> +        #[test]
> +        fn default_capacity() {
> +            assert_eq!(EdgeEventBuffer::new(0).unwrap().get_capacity(), 64);
> +        }
> +
> +        #[test]
> +        fn user_defined_capacity() {
> +            assert_eq!(EdgeEventBuffer::new(123).unwrap().get_capacity(), 123);
> +        }
> +
> +        #[test]
> +        fn max_capacity() {
> +            assert_eq!(EdgeEventBuffer::new(1024 * 2).unwrap().get_capacity(), 1024);
> +        }
> +    }
> +
> +    mod failure {
> +        use super::*;
> +

Don't see the point of the failure/verify namespace level here.

> +        #[test]
> +        fn wait_timeout() {
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[0]));
> +            config.lconfig_edge(Some(Edge::Both));
> +            config.request_lines().unwrap();
> +
> +            // No events available
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .wait_edge_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +        }

Is a timeout really a "failure"?

It is testing wait_edge_event(), which is a method of line_request,
and so should be in the line_request test suite.

> +
> +        #[test]
> +        fn dir_out_edge_failure() {
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[0]));
> +            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
> +
> +            assert_eq!(
> +                config.request_lines().unwrap_err(),
> +                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
> +            );
> +        }
> +    }
> +

This is testing a failure mode of request_lines(), not edge_events.
Where is the edge_event here?

> +    mod verify {
> +        use super::*;
> +
> +        // Helpers to generate events
> +        fn trigger_falling_and_rising_edge(sim: Arc<Sim>, offset: u32) {
> +            spawn(move || {
> +                sleep(Duration::from_millis(30));
> +                sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
> +
> +                sleep(Duration::from_millis(30));
> +                sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
> +            });
> +        }
> +
> +        fn trigger_rising_edge_events_on_two_offsets(sim: Arc<Sim>, offset: [u32; 2]) {
> +            spawn(move || {
> +                sleep(Duration::from_millis(30));
> +                sim.set_pull(offset[0], GPIOSIM_PULL_UP as i32).unwrap();
> +
> +                sleep(Duration::from_millis(30));
> +                sim.set_pull(offset[1], GPIOSIM_PULL_UP as i32).unwrap();
> +            });
> +        }
> +
> +        fn trigger_multiple_events(sim: Arc<Sim>, offset: u32) {
> +            sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
> +            sleep(Duration::from_millis(10));
> +
> +            sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
> +            sleep(Duration::from_millis(10));
> +
> +            sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
> +            sleep(Duration::from_millis(10));
> +        }
> +
> +        #[test]
> +        fn both_edges() {
> +            const GPIO: u32 = 2;
> +            let buf = EdgeEventBuffer::new(0).unwrap();
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[GPIO]));
> +            config.lconfig_edge(Some(Edge::Both));
> +            config.request_lines().unwrap();
> +
> +            // Generate events
> +            trigger_falling_and_rising_edge(config.sim(), GPIO);
> +
> +            // Rising event
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                1
> +            );
> +            assert_eq!(buf.get_num_events(), 1);
> +
> +            let event = buf.get_event(0).unwrap();
> +            let ts_rising = event.get_timestamp();
> +            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
> +            assert_eq!(event.get_line_offset(), GPIO);
> +
> +            // Falling event
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                1
> +            );
> +            assert_eq!(buf.get_num_events(), 1);
> +
> +            let event = buf.get_event(0).unwrap();
> +            let ts_falling = event.get_timestamp();
> +            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Falling);
> +            assert_eq!(event.get_line_offset(), GPIO);
> +
> +            // No events available
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .wait_edge_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +
> +            assert!(ts_falling > ts_rising);
> +        }
> +
> +        #[test]
> +        fn rising_edge() {
> +            const GPIO: u32 = 6;
> +            let buf = EdgeEventBuffer::new(0).unwrap();
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[GPIO]));
> +            config.lconfig_edge(Some(Edge::Rising));
> +            config.request_lines().unwrap();
> +
> +            // Generate events
> +            trigger_falling_and_rising_edge(config.sim(), GPIO);
> +
> +            // Rising event
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                1
> +            );
> +            assert_eq!(buf.get_num_events(), 1);
> +
> +            let event = buf.get_event(0).unwrap();
> +            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
> +            assert_eq!(event.get_line_offset(), GPIO);
> +
> +            // No events available
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .wait_edge_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +        }
> +
> +        #[test]
> +        fn falling_edge() {
> +            const GPIO: u32 = 7;
> +            let buf = EdgeEventBuffer::new(0).unwrap();
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[GPIO]));
> +            config.lconfig_edge(Some(Edge::Falling));
> +            config.request_lines().unwrap();
> +
> +            // Generate events
> +            trigger_falling_and_rising_edge(config.sim(), GPIO);
> +
> +            // Falling event
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                1
> +            );
> +            assert_eq!(buf.get_num_events(), 1);
> +
> +            let event = buf.get_event(0).unwrap();
> +            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Falling);
> +            assert_eq!(event.get_line_offset(), GPIO);
> +
> +            // No events available
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .wait_edge_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +        }
> +
> +        #[test]
> +        fn edge_sequence() {
> +            const GPIO: [u32; 2] = [0, 1];
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&GPIO));
> +            config.lconfig_edge(Some(Edge::Both));
> +            config.request_lines().unwrap();
> +
> +            // Generate events
> +            trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO);
> +
> +            // Rising event GPIO 0
> +            let buf = EdgeEventBuffer::new(0).unwrap();
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                1
> +            );
> +            assert_eq!(buf.get_num_events(), 1);
> +
> +            let event = buf.get_event(0).unwrap();
> +            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
> +            assert_eq!(event.get_line_offset(), GPIO[0]);
> +            assert_eq!(event.get_global_seqno(), 1);
> +            assert_eq!(event.get_line_seqno(), 1);
> +
> +            // Rising event GPIO 1
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                1
> +            );
> +            assert_eq!(buf.get_num_events(), 1);
> +
> +            let event = buf.get_event(0).unwrap();
> +            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
> +            assert_eq!(event.get_line_offset(), GPIO[1]);
> +            assert_eq!(event.get_global_seqno(), 2);
> +            assert_eq!(event.get_line_seqno(), 1);
> +
> +            // No events available
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .wait_edge_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +        }
> +
> +        #[test]
> +        fn multiple_events() {
> +            const GPIO: u32 = 1;
> +            let buf = EdgeEventBuffer::new(0).unwrap();
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[GPIO]));
> +            config.lconfig_edge(Some(Edge::Both));
> +            config.request_lines().unwrap();
> +
> +            // Generate events
> +            trigger_multiple_events(config.sim(), GPIO);
> +
> +            // Read multiple events
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                3
> +            );
> +            assert_eq!(buf.get_num_events(), 3);
> +
> +            let mut global_seqno = 1;
> +            let mut line_seqno = 1;
> +
> +            // Verify sequence number of events
> +            for i in 0..3 {
> +                let event = buf.get_event(i).unwrap();
> +                assert_eq!(event.get_line_offset(), GPIO);
> +                assert_eq!(event.get_global_seqno(), global_seqno);
> +                assert_eq!(event.get_line_seqno(), line_seqno);
> +
> +                global_seqno += 1;
> +                line_seqno += 1;
> +            }
> +        }
> +
> +        #[test]
> +        fn over_capacity() {
> +            const GPIO: u32 = 2;
> +            let buf = EdgeEventBuffer::new(2).unwrap();
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[GPIO]));
> +            config.lconfig_edge(Some(Edge::Both));
> +            config.request_lines().unwrap();
> +
> +            // Generate events
> +            trigger_multiple_events(config.sim(), GPIO);
> +
> +            // Read multiple events
> +            config
> +                .request()
> +                .wait_edge_event(Duration::from_secs(1))
> +                .unwrap();
> +
> +            assert_eq!(
> +                config
> +                    .request()
> +                    .read_edge_event(&buf, buf.get_capacity())
> +                    .unwrap(),
> +                2
> +            );
> +            assert_eq!(buf.get_num_events(), 2);
> +        }
> +    }
> +}
> diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs
> new file mode 100644
> index 000000000000..96d8385deadf
> --- /dev/null
> +++ b/bindings/rust/tests/info_event.rs
> @@ -0,0 +1,126 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod info_event {
> +    use libc::EINVAL;
> +    use std::sync::Arc;
> +    use std::thread::{sleep, spawn};
> +    use std::time::Duration;
> +
> +    use vmm_sys_util::errno::Error as IoError;
> +
> +    use crate::common::*;
> +    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
> +
> +    fn request_reconfigure_line(chip: Arc<Chip>) {
> +        spawn(move || {
> +            sleep(Duration::from_millis(10));
> +
> +            let lconfig1 = LineConfig::new().unwrap();
> +            let rconfig = RequestConfig::new().unwrap();
> +            rconfig.set_offsets(&[7]);
> +
> +            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
> +
> +            sleep(Duration::from_millis(10));
> +
> +            let mut lconfig2 = LineConfig::new().unwrap();
> +            lconfig2.set_direction_default(Direction::Output);
> +
> +            request.reconfigure_lines(&lconfig2).unwrap();
> +
> +            sleep(Duration::from_millis(10));
> +        });
> +    }
> +

Benefit of the background thread?

> +    mod watch {
> +        use super::*;
> +        const NGPIO: u64 = 8;
> +        const GPIO: u32 = 7;
> +
> +        #[test]
> +        fn failure() {
> +            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
> +            let chip = Chip::open(sim.dev_path()).unwrap();
> +
> +            assert_eq!(
> +                chip.watch_line_info(NGPIO as u32).unwrap_err(),
> +                ChipError::OperationFailed("Gpio LineInfo line-info", IoError::new(EINVAL))
> +            );
> +
> +            chip.watch_line_info(3).unwrap();
> +
> +            // No events available
> +            assert_eq!(
> +                chip.wait_info_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +        }
> +
> +        #[test]
> +        fn verify() {
> +            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
> +            let chip = Chip::open(sim.dev_path()).unwrap();
> +            let info = chip.watch_line_info(GPIO).unwrap();
> +
> +            assert_eq!(info.get_offset(), GPIO);
> +        }
> +
> +        #[test]
> +        fn reconfigure() {
> +            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
> +            let chip = Arc::new(Chip::open(sim.dev_path()).unwrap());
> +            let info = chip.watch_line_info(GPIO).unwrap();
> +
> +            assert_eq!(info.get_direction().unwrap(), Direction::Input);
> +
> +            // Generate events
> +            request_reconfigure_line(chip.clone());
> +
> +            // Line requested event
> +            chip.wait_info_event(Duration::from_secs(1)).unwrap();
> +            let event = chip.read_info_event().unwrap();
> +            let ts_req = event.get_timestamp();
> +
> +            assert_eq!(event.get_event_type().unwrap(), Event::LineRequested);
> +            assert_eq!(
> +                event.line_info().unwrap().get_direction().unwrap(),
> +                Direction::Input
> +            );
> +
> +            // Line changed event
> +            chip.wait_info_event(Duration::from_secs(1)).unwrap();
> +            let event = chip.read_info_event().unwrap();
> +            let ts_rec = event.get_timestamp();
> +
> +            assert_eq!(event.get_event_type().unwrap(), Event::LineConfigChanged);
> +            assert_eq!(
> +                event.line_info().unwrap().get_direction().unwrap(),
> +                Direction::Output
> +            );
> +
> +            // Line released event
> +            chip.wait_info_event(Duration::from_secs(1)).unwrap();
> +            let event = chip.read_info_event().unwrap();
> +            let ts_rel = event.get_timestamp();
> +
> +            assert_eq!(event.get_event_type().unwrap(), Event::LineReleased);
> +
> +            // No events available
> +            assert_eq!(
> +                chip.wait_info_event(Duration::from_millis(100))
> +                    .unwrap_err(),
> +                ChipError::OperationTimedOut
> +            );
> +
> +            // Check timestamps are really monotonic.
> +            assert!(ts_rel > ts_rec);
> +            assert!(ts_rec > ts_req);
> +        }
> +    }
> +}
> diff --git a/bindings/rust/tests/line_config.rs b/bindings/rust/tests/line_config.rs
> new file mode 100644
> index 000000000000..82879324a7f0
> --- /dev/null
> +++ b/bindings/rust/tests/line_config.rs
> @@ -0,0 +1,187 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod line_config {
> +    use std::time::Duration;
> +
> +    use libgpiod::{Bias, Direction, Drive, Edge, EventClock, LineConfig};
> +
> +    mod default {
> +        use super::*;
> +
> +        #[test]
> +        fn verify() {
> +            let lconfig = LineConfig::new().unwrap();
> +
> +            assert_eq!(lconfig.get_direction_default().unwrap(), Direction::AsIs);
> +            assert_eq!(lconfig.get_edge_detection_default().unwrap(), Edge::None);
> +            assert_eq!(lconfig.get_bias_default().unwrap(), Bias::AsIs);
> +            assert_eq!(lconfig.get_drive_default().unwrap(), Drive::PushPull);
> +            assert_eq!(lconfig.get_active_low_default(), false);
> +            assert_eq!(
> +                lconfig.get_debounce_period_default().unwrap(),
> +                Duration::from_millis(0)
> +            );
> +            assert_eq!(
> +                lconfig.get_event_clock_default().unwrap(),
> +                EventClock::Monotonic
> +            );
> +            assert_eq!(lconfig.get_output_value_default().unwrap(), 0);
> +            assert_eq!(lconfig.get_overrides().unwrap().len(), 0);
> +        }
> +    }

The only test in the default mod is verify.
Drop the mod and rename the test to default.

> +
> +    mod overrides {
> +        use super::*;
> +
> +        #[test]
> +        fn direction() {
> +            const GPIO: u32 = 0;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_direction_default(Direction::AsIs);
> +            lconfig.set_direction_override(Direction::Input, GPIO);
> +
> +            assert_eq!(lconfig.direction_is_overridden(GPIO), true);
> +            assert_eq!(
> +                lconfig.get_direction_offset(GPIO).unwrap(),
> +                Direction::Input
> +            );
> +
> +            lconfig.clear_direction_override(GPIO);
> +            assert_eq!(lconfig.direction_is_overridden(GPIO), false);
> +            assert_eq!(lconfig.get_direction_offset(GPIO).unwrap(), Direction::AsIs);
> +        }
> +
> +        #[test]
> +        fn edge_detection() {
> +            const GPIO: u32 = 1;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_edge_detection_default(Edge::None);
> +            lconfig.set_edge_detection_override(Edge::Both, GPIO);
> +
> +            assert_eq!(lconfig.edge_detection_is_overridden(GPIO), true);
> +            assert_eq!(lconfig.get_edge_detection_offset(GPIO).unwrap(), Edge::Both);
> +
> +            lconfig.clear_edge_detection_override(GPIO);
> +            assert_eq!(lconfig.edge_detection_is_overridden(GPIO), false);
> +            assert_eq!(lconfig.get_edge_detection_offset(GPIO).unwrap(), Edge::None);
> +        }
> +
> +        #[test]
> +        fn bias() {
> +            const GPIO: u32 = 2;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_bias_default(Bias::AsIs);
> +            lconfig.set_bias_override(Bias::PullDown, GPIO);
> +
> +            assert_eq!(lconfig.bias_is_overridden(GPIO), true);
> +            assert_eq!(lconfig.get_bias_offset(GPIO).unwrap(), Bias::PullDown);
> +
> +            lconfig.clear_bias_override(GPIO);
> +            assert_eq!(lconfig.bias_is_overridden(GPIO), false);
> +            assert_eq!(lconfig.get_bias_offset(GPIO).unwrap(), Bias::AsIs);
> +        }
> +
> +        #[test]
> +        fn drive() {
> +            const GPIO: u32 = 3;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_drive_default(Drive::PushPull);
> +            lconfig.set_drive_override(Drive::OpenDrain, GPIO);
> +
> +            assert_eq!(lconfig.drive_is_overridden(GPIO), true);
> +            assert_eq!(lconfig.get_drive_offset(GPIO).unwrap(), Drive::OpenDrain);
> +
> +            lconfig.clear_drive_override(GPIO);
> +            assert_eq!(lconfig.drive_is_overridden(GPIO), false);
> +            assert_eq!(lconfig.get_drive_offset(GPIO).unwrap(), Drive::PushPull);
> +        }
> +
> +        #[test]
> +        fn active_low() {
> +            const GPIO: u32 = 4;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_active_low_default(false);
> +            lconfig.set_active_low_override(true, GPIO);
> +
> +            assert_eq!(lconfig.active_low_is_overridden(GPIO), true);
> +            assert_eq!(lconfig.get_active_low_offset(GPIO), true);
> +
> +            lconfig.clear_active_low_override(GPIO);
> +            assert_eq!(lconfig.active_low_is_overridden(GPIO), false);
> +            assert_eq!(lconfig.get_active_low_offset(GPIO), false);
> +        }
> +
> +        #[test]
> +        fn debounce_period() {
> +            const GPIO: u32 = 5;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_debounce_period_default(Duration::from_millis(5));
> +            lconfig.set_debounce_period_override(Duration::from_millis(3), GPIO);
> +
> +            assert_eq!(lconfig.debounce_period_is_overridden(GPIO), true);
> +            assert_eq!(
> +                lconfig.get_debounce_period_offset(GPIO).unwrap(),
> +                Duration::from_millis(3)
> +            );
> +
> +            lconfig.clear_debounce_period_override(GPIO);
> +            assert_eq!(lconfig.debounce_period_is_overridden(GPIO), false);
> +            assert_eq!(
> +                lconfig.get_debounce_period_offset(GPIO).unwrap(),
> +                Duration::from_millis(5)
> +            );
> +        }
> +
> +        #[test]
> +        fn event_clock() {
> +            const GPIO: u32 = 6;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_event_clock_default(EventClock::Monotonic);
> +            lconfig.set_event_clock_override(EventClock::Realtime, GPIO);
> +
> +            assert_eq!(lconfig.event_clock_is_overridden(GPIO), true);
> +            assert_eq!(
> +                lconfig.get_event_clock_offset(GPIO).unwrap(),
> +                EventClock::Realtime
> +            );
> +
> +            lconfig.clear_event_clock_override(GPIO);
> +            assert_eq!(lconfig.event_clock_is_overridden(GPIO), false);
> +            assert_eq!(
> +                lconfig.get_event_clock_offset(GPIO).unwrap(),
> +                EventClock::Monotonic
> +            );
> +        }
> +
> +        #[test]
> +        fn output_value() {
> +            const GPIO: u32 = 0;
> +            let mut lconfig = LineConfig::new().unwrap();
> +
> +            lconfig.set_output_value_default(0);
> +            lconfig.set_output_value_override(1, GPIO);
> +            lconfig.set_output_values(&[1, 2, 8], &[1, 1, 1]).unwrap();
> +
> +            for line in [0, 1, 2, 8] {
> +                assert_eq!(lconfig.output_value_is_overridden(line), true);
> +                assert_eq!(lconfig.get_output_value_offset(line).unwrap(), 1);
> +
> +                lconfig.clear_output_value_override(line);
> +                assert_eq!(lconfig.output_value_is_overridden(line), false);
> +                assert_eq!(lconfig.get_output_value_offset(line).unwrap(), 0);
> +            }
> +        }
> +    }
> +}
> diff --git a/bindings/rust/tests/line_info.rs b/bindings/rust/tests/line_info.rs
> new file mode 100644
> index 000000000000..f6f8f592cc85
> --- /dev/null
> +++ b/bindings/rust/tests/line_info.rs
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod line_info {
> +    use libc::EINVAL;
> +    use std::time::Duration;
> +
> +    use vmm_sys_util::errno::Error as IoError;
> +
> +    use crate::common::*;
> +    use libgpiod::{Bias, Chip, Direction, Drive, Edge, Error as ChipError, EventClock};
> +    use libgpiod_sys::{GPIOSIM_HOG_DIR_OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_LOW};
> +
> +    const NGPIO: u64 = 8;
> +
> +    mod basic {
> +        use super::*;
> +
> +        #[test]
> +        fn verify() {
> +            const GPIO: u32 = 0;
> +            const LABEL: &str = "foobar";
> +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> +            sim.set_line_name(GPIO, LABEL).unwrap();
> +            sim.hog_line(GPIO, "hog", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
> +                .unwrap();
> +            sim.enable().unwrap();
> +
> +            let chip = Chip::open(sim.dev_path()).unwrap();
> +            let info = chip.line_info(GPIO).unwrap();
> +
> +            assert_eq!(info.get_offset(), GPIO);
> +            assert_eq!(info.get_name().unwrap(), LABEL);
> +            assert_eq!(info.is_used(), true);
> +            assert_eq!(info.get_consumer().unwrap(), "hog");
> +            assert_eq!(info.get_direction().unwrap(), Direction::Output);
> +            assert_eq!(info.is_active_low(), false);
> +            assert_eq!(info.get_bias().unwrap(), Bias::Unknown);
> +            assert_eq!(info.get_drive().unwrap(), Drive::PushPull);
> +            assert_eq!(info.get_edge_detection().unwrap(), Edge::None);
> +            assert_eq!(info.get_event_clock().unwrap(), EventClock::Monotonic);
> +            assert_eq!(info.is_debounced(), false);
> +            assert_eq!(info.get_debounce_period(), Duration::from_millis(0));
> +
> +            assert_eq!(
> +                chip.line_info(NGPIO as u32).unwrap_err(),
> +                ChipError::OperationFailed("Gpio LineInfo line-info", IoError::new(EINVAL))
> +            );
> +        }
> +    }
> +
> +    mod properties {
> +        use super::*;
> +
> +        #[test]
> +        fn verify() {
> +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> +            sim.set_line_name(1, "one").unwrap();
> +            sim.set_line_name(2, "two").unwrap();
> +            sim.set_line_name(4, "four").unwrap();
> +            sim.set_line_name(5, "five").unwrap();
> +            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
> +                .unwrap();
> +            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
> +                .unwrap();
> +            sim.enable().unwrap();
> +
> +            let chip = Chip::open(sim.dev_path()).unwrap();
> +            chip.line_info(6).unwrap();
> +
> +            let info4 = chip.line_info(4).unwrap();
> +            assert_eq!(info4.get_offset(), 4);
> +            assert_eq!(info4.get_name().unwrap(), "four");
> +            assert_eq!(info4.is_used(), true);
> +            assert_eq!(info4.get_consumer().unwrap(), "hog4");
> +            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
> +            assert_eq!(info4.is_active_low(), false);
> +            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
> +            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
> +            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
> +            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
> +            assert_eq!(info4.is_debounced(), false);
> +            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
> +        }
> +    }

Test that you can read all supported values for all fields.

> +}
> diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs
> new file mode 100644
> index 000000000000..361ee6318d2e
> --- /dev/null
> +++ b/bindings/rust/tests/line_request.rs
> @@ -0,0 +1,234 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod line_request {
> +    use libc::{EBUSY, EINVAL};
> +
> +    use vmm_sys_util::errno::Error as IoError;
> +
> +    use crate::common::*;
> +    use libgpiod::{Bias, Direction, Error as ChipError, LineConfig};
> +    use libgpiod_sys::{
> +        GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP, GPIOSIM_VALUE_ACTIVE, GPIOSIM_VALUE_INACTIVE,
> +    };
> +
> +    const NGPIO: u64 = 8;
> +
> +    mod invalid_arguments {
> +        use super::*;
> +
> +        #[test]
> +        fn no_offsets() {
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(None);
> +            config.lconfig_raw();
> +
> +            assert_eq!(
> +                config.request_lines().unwrap_err(),
> +                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
> +            );
> +        }
> +
> +        #[test]
> +        fn duplicate_offsets() {
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[2, 0, 0, 4]));
> +            config.lconfig_raw();
> +
> +            assert_eq!(
> +                config.request_lines().unwrap_err(),
> +                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EBUSY))
> +            );
> +        }
> +
> +        #[test]
> +        fn out_of_bound_offsets() {
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[2, 0, 8, 4]));
> +            config.lconfig_raw();
> +
> +            assert_eq!(
> +                config.request_lines().unwrap_err(),
> +                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
> +            );
> +        }
> +    }
> +
> +    mod verify {
> +        use super::*;
> +
> +        #[test]
> +        fn custom_consumer() {
> +            const GPIO: u32 = 2;
> +            const CONSUMER: &str = "foobar";
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig_consumer(Some(&[GPIO]), Some(CONSUMER));
> +            config.lconfig_raw();
> +            config.request_lines().unwrap();
> +
> +            let info = config.chip().line_info(GPIO).unwrap();
> +
> +            assert_eq!(info.is_used(), true);
> +            assert_eq!(info.get_consumer().unwrap(), CONSUMER);
> +        }
> +
> +        #[test]
> +        fn empty_consumer() {
> +            const GPIO: u32 = 2;
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&[GPIO]));
> +            config.lconfig_raw();
> +            config.request_lines().unwrap();
> +
> +            let info = config.chip().line_info(GPIO).unwrap();
> +
> +            assert_eq!(info.is_used(), true);
> +            assert_eq!(info.get_consumer().unwrap(), "?");
> +        }
> +
> +        #[test]
> +        fn read_values() {
> +            let offsets = [7, 1, 0, 6, 2];
> +            let pulls = [
> +                GPIOSIM_PULL_UP,
> +                GPIOSIM_PULL_UP,
> +                GPIOSIM_PULL_DOWN,
> +                GPIOSIM_PULL_UP,
> +                GPIOSIM_PULL_DOWN,
> +            ];
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.set_pull(&offsets, &pulls);
> +            config.rconfig(Some(&offsets));
> +            config.lconfig(Some(Direction::Input), None, None, None, None);
> +            config.request_lines().unwrap();
> +
> +            let request = config.request();
> +
> +            // Buffer is smaller
> +            let mut values: Vec<i32> = vec![0; 4];
> +            assert_eq!(
> +                request.get_values(&mut values).unwrap_err(),
> +                ChipError::OperationFailed(
> +                    "Gpio LineRequest array size mismatch",
> +                    IoError::new(EINVAL),
> +                )
> +            );
> +
> +            // Buffer is larger
> +            let mut values: Vec<i32> = vec![0; 6];
> +            assert_eq!(
> +                request.get_values(&mut values).unwrap_err(),
> +                ChipError::OperationFailed(
> +                    "Gpio LineRequest array size mismatch",
> +                    IoError::new(EINVAL),
> +                )
> +            );
> +
> +            // Single values read properly
> +            assert_eq!(request.get_value(7).unwrap(), 1);
> +
> +            // Values read properly
> +            let mut values: Vec<i32> = vec![0; 5];
> +            request.get_values(&mut values).unwrap();
> +            for i in 0..values.len() {
> +                assert_eq!(
> +                    values[i],
> +                    match pulls[i] {
> +                        GPIOSIM_PULL_DOWN => 0,
> +                        _ => 1,
> +                    }
> +                );
> +            }
> +
> +            // Subset of values read properly
> +            let mut values: Vec<i32> = vec![0; 3];
> +            request.get_values_subset(&[2, 0, 6], &mut values).unwrap();
> +            assert_eq!(values[0], 0);
> +            assert_eq!(values[1], 0);
> +            assert_eq!(values[2], 1);
> +
> +            // Value read properly after reconfigure
> +            let mut lconfig = LineConfig::new().unwrap();
> +            lconfig.set_active_low_default(true);
> +            request.reconfigure_lines(&lconfig).unwrap();
> +            assert_eq!(request.get_value(7).unwrap(), 0);
> +        }
> +
> +        #[test]
> +        fn set_output_values() {
> +            let offsets = [0, 1, 3, 4];
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&offsets));
> +            config.lconfig(Some(Direction::Output), Some(1), Some((4, 0)), None, None);
> +            config.request_lines().unwrap();
> +
> +            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +
> +            // Overriden
> +            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +
> +            // Default
> +            assert_eq!(config.sim().val(2).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +        }
> +
> +        #[test]
> +        fn reconfigure_output_values() {
> +            let offsets = [0, 1, 3, 4];
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&offsets));
> +            config.lconfig(Some(Direction::Output), Some(0), None, None, None);
> +            config.request_lines().unwrap();
> +            let request = config.request();
> +
> +            // Set single value
> +            request.set_value(1, 1).unwrap();
> +            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            request.set_value(1, 0).unwrap();
> +            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +
> +            // Set values of subset
> +            request.set_values_subset(&[4, 3], &[1, 1]).unwrap();
> +            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            request.set_values_subset(&[4, 3], &[0, 0]).unwrap();
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +
> +            // Set all values
> +            request.set_values(&[1, 0, 1, 0]).unwrap();
> +            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            request.set_values(&[0, 0, 0, 0]).unwrap();
> +            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
> +        }
> +
> +        #[test]
> +        fn set_bias() {
> +            let offsets = [3];
> +            let mut config = TestConfig::new(NGPIO).unwrap();
> +            config.rconfig(Some(&offsets));
> +            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
> +            config.request_lines().unwrap();
> +            config.request();
> +
> +            // Set single value
> +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
> +        }
> +    }

Test reconfigure() failure modes.

Test you can reconfigure all values for all attributes (not all values
for debounce obviously - just the flags).

Similarly for request_lines (ideally in chip.rs)

> +}
> diff --git a/bindings/rust/tests/request_config.rs b/bindings/rust/tests/request_config.rs
> new file mode 100644
> index 000000000000..e914ca8ec887
> --- /dev/null
> +++ b/bindings/rust/tests/request_config.rs
> @@ -0,0 +1,42 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +mod common;
> +
> +mod request_config {
> +    use vmm_sys_util::errno::Error as IoError;
> +
> +    use libgpiod::{Error as ChipError, RequestConfig};
> +
> +    mod verify {
> +        use super::*;
> +
> +        #[test]
> +        fn default() {
> +            let rconfig = RequestConfig::new().unwrap();
> +
> +            assert_eq!(rconfig.get_offsets().len(), 0);
> +            assert_eq!(rconfig.get_event_buffer_size(), 0);
> +            assert_eq!(
> +                rconfig.get_consumer().unwrap_err(),
> +                ChipError::OperationFailed("Gpio RequestConfig get-consumer", IoError::new(0))
> +            );
> +        }
> +
> +        #[test]
> +        fn initialized() {
> +            let offsets = [0, 1, 2, 3];
> +            const CONSUMER: &str = "foobar";
> +            let rconfig = RequestConfig::new().unwrap();
> +            rconfig.set_consumer(CONSUMER);
> +            rconfig.set_offsets(&offsets);
> +            rconfig.set_event_buffer_size(64);
> +
> +            assert_eq!(rconfig.get_offsets(), offsets);
> +            assert_eq!(rconfig.get_event_buffer_size(), 64);
> +            assert_eq!(rconfig.get_consumer().unwrap(), CONSUMER);
> +        }
> +    }
> +}
> -- 
> 2.31.1.272.g89b43f80a514
> 

Clippy warnings to fix:

$cargo clippy --tests
warning: this expression creates a reference which is immediately dereferenced by the compiler
   --> tests/common/config.rs:103:9
    |
103 |         &self.chip.as_ref().unwrap()
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `self.chip.as_ref().unwrap()`
    |
    = note: `#[warn(clippy::needless_borrow)]` on by default
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

warning: this expression creates a reference which is immediately dereferenced by the compiler
   --> tests/common/config.rs:107:9
    |
107 |         &self.request.as_ref().unwrap()
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `self.request.as_ref().unwrap()`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:24:13
   |
24 |             assert_eq!(lconfig.get_active_low_default(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = note: `#[warn(clippy::bool_assert_comparison)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:49:13
   |
49 |             assert_eq!(lconfig.direction_is_overridden(GPIO), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:56:13
   |
56 |             assert_eq!(lconfig.direction_is_overridden(GPIO), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:68:13
   |
68 |             assert_eq!(lconfig.edge_detection_is_overridden(GPIO), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:72:13
   |
72 |             assert_eq!(lconfig.edge_detection_is_overridden(GPIO), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:84:13
   |
84 |             assert_eq!(lconfig.bias_is_overridden(GPIO), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_config.rs:88:13
   |
88 |             assert_eq!(lconfig.bias_is_overridden(GPIO), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:100:13
    |
100 |             assert_eq!(lconfig.drive_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:104:13
    |
104 |             assert_eq!(lconfig.drive_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:116:13
    |
116 |             assert_eq!(lconfig.active_low_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:117:13
    |
117 |             assert_eq!(lconfig.get_active_low_offset(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:120:13
    |
120 |             assert_eq!(lconfig.active_low_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:121:13
    |
121 |             assert_eq!(lconfig.get_active_low_offset(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:132:13
    |
132 |             assert_eq!(lconfig.debounce_period_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:139:13
    |
139 |             assert_eq!(lconfig.debounce_period_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:154:13
    |
154 |             assert_eq!(lconfig.event_clock_is_overridden(GPIO), true);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:161:13
    |
161 |             assert_eq!(lconfig.event_clock_is_overridden(GPIO), false);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:178:17
    |
178 |                 assert_eq!(lconfig.output_value_is_overridden(line), true);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
   --> tests/line_config.rs:182:17
    |
182 |                 assert_eq!(lconfig.output_value_is_overridden(line), false);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_info.rs:38:13
   |
38 |             assert_eq!(info.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = note: `#[warn(clippy::bool_assert_comparison)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_info.rs:41:13
   |
41 |             assert_eq!(info.is_active_low(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_info.rs:46:13
   |
46 |             assert_eq!(info.is_debounced(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_info.rs:78:13
   |
78 |             assert_eq!(info4.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_info.rs:81:13
   |
81 |             assert_eq!(info4.is_active_low(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_info.rs:86:13
   |
86 |             assert_eq!(info4.is_debounced(), false);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_request.rs:75:13
   |
75 |             assert_eq!(info.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = note: `#[warn(clippy::bool_assert_comparison)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: used `assert_eq!` with a literal bool
  --> tests/line_request.rs:89:13
   |
89 |             assert_eq!(info.is_used(), true);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison

warning: `libgpiod` (test "line_config") generated 21 warnings
warning: `libgpiod` (test "request_config") generated 2 warnings (2 duplicates)
warning: `libgpiod` (test "info_event") generated 2 warnings (2 duplicates)
warning: `libgpiod` (test "line_info") generated 8 warnings (2 duplicates)
warning: `libgpiod` (test "chip") generated 2 warnings (2 duplicates)
warning: `libgpiod` (test "line_request") generated 4 warnings (2 duplicates)
warning: `libgpiod` (test "edge_event") generated 2 warnings (2 duplicates)
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s


Cheers,
Kent.
Viresh Kumar July 27, 2022, 9:59 a.m. UTC | #2
On 27-07-22, 10:58, Kent Gibson wrote:
> On Fri, Jul 08, 2022 at 05:05:00PM +0530, Viresh Kumar wrote:
> Don't include the test results in the commit message.
> Those are more of a release artifact than a commit artifact.
> It is assumed the tests pass for you or you wouldn't be submitting them.

I wasn't trying to prove that I tested them :)

The idea was to show how module/test names eventually look in the output.

Maybe I could have just replied to this email and pasted it, sure commit message
doesn't need it.

> > diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs
> > +    mod configure {
> > +        use super::*;
> > +        const NGPIO: u64 = 16;
> > +        const LABEL: &str = "foobar";
> > +
> > +        #[test]
> > +        fn verify() {
> > +            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
> > +            let chip = Chip::open(sim.dev_path()).unwrap();
> > +
> > +            assert_eq!(chip.get_label().unwrap(), LABEL);
> > +            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
> > +            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
> > +            assert_eq!(chip.get_num_lines(), NGPIO as u32);
> > +            chip.get_fd().unwrap();
> > +        }
> > +
> 
> A test for a basic open on an existing chip and a smoke test of some
> chip methods is called "verify", so chip::configure::verify?

You want me to rename this ? Sorry, got confused :(

Yeah, I am generally bad with naming, suggestions are welcome here :)

> > +        #[test]
> > +        fn line_lookup() {
> > +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> > +            sim.set_line_name(0, "zero").unwrap();
> > +            sim.set_line_name(2, "two").unwrap();
> > +            sim.set_line_name(3, "three").unwrap();
> > +            sim.set_line_name(5, "five").unwrap();
> > +            sim.set_line_name(10, "ten").unwrap();
> > +            sim.set_line_name(11, "ten").unwrap();
> > +            sim.enable().unwrap();
> > +
> > +            let chip = Chip::open(sim.dev_path()).unwrap();
> > +
> > +            // Success case
> > +            assert_eq!(chip.find_line("zero").unwrap(), 0);
> > +            assert_eq!(chip.find_line("two").unwrap(), 2);
> > +            assert_eq!(chip.find_line("three").unwrap(), 3);
> > +            assert_eq!(chip.find_line("five").unwrap(), 5);
> > +
> > +            // Success with duplicate names, should return first entry
> > +            assert_eq!(chip.find_line("ten").unwrap(), 10);
> > +
> > +            // Failure
> > +            assert_eq!(
> > +                chip.find_line("nonexistent").unwrap_err(),
> > +                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
> > +            );
> > +        }
> 
> If it is testing find_line() then why not call it find_line?

I think I picked many names from the TEST_CASE name from cxx bindings. This was
written there as:

TEST_CASE("line lookup by name works", "[chip]")

and I didn't think much about it :)

Sure I can name this find_line().

> > diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs
> > +        #[test]
> > +        fn wait_timeout() {
> > +            let mut config = TestConfig::new(NGPIO).unwrap();
> > +            config.rconfig(Some(&[0]));
> > +            config.lconfig_edge(Some(Edge::Both));
> > +            config.request_lines().unwrap();
> > +
> > +            // No events available
> > +            assert_eq!(
> > +                config
> > +                    .request()
> > +                    .wait_edge_event(Duration::from_millis(100))
> > +                    .unwrap_err(),
> > +                ChipError::OperationTimedOut
> > +            );
> > +        }
> 
> Is a timeout really a "failure"?
> 
> It is testing wait_edge_event(), which is a method of line_request,
> and so should be in the line_request test suite.

Copied it from cxx again, I just tried to add similar tests in similar files.

TEST_CASE("edge_event wait timeout", "[edge-event]")

> > +
> > +        #[test]
> > +        fn dir_out_edge_failure() {
> > +            let mut config = TestConfig::new(NGPIO).unwrap();
> > +            config.rconfig(Some(&[0]));
> > +            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
> > +
> > +            assert_eq!(
> > +                config.request_lines().unwrap_err(),
> > +                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
> > +            );
> > +        }
> > +    }
> > +
> 
> This is testing a failure mode of request_lines(), not edge_events.
> Where is the edge_event here?

I agree, I will move this out.

This needs fixing too though.

TEST_CASE("output mode and edge detection don't work together", "[edge-event]")

> > diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs
> > new file mode 100644
> > index 000000000000..96d8385deadf
> > --- /dev/null
> > +++ b/bindings/rust/tests/info_event.rs
> > @@ -0,0 +1,126 @@
> > +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> > +//
> > +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> > +//     Viresh Kumar <viresh.kumar@linaro.org>
> > +
> > +mod common;
> > +
> > +mod info_event {
> > +    use libc::EINVAL;
> > +    use std::sync::Arc;
> > +    use std::thread::{sleep, spawn};
> > +    use std::time::Duration;
> > +
> > +    use vmm_sys_util::errno::Error as IoError;
> > +
> > +    use crate::common::*;
> > +    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
> > +
> > +    fn request_reconfigure_line(chip: Arc<Chip>) {
> > +        spawn(move || {
> > +            sleep(Duration::from_millis(10));
> > +
> > +            let lconfig1 = LineConfig::new().unwrap();
> > +            let rconfig = RequestConfig::new().unwrap();
> > +            rconfig.set_offsets(&[7]);
> > +
> > +            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
> > +
> > +            sleep(Duration::from_millis(10));
> > +
> > +            let mut lconfig2 = LineConfig::new().unwrap();
> > +            lconfig2.set_direction_default(Direction::Output);
> > +
> > +            request.reconfigure_lines(&lconfig2).unwrap();
> > +
> > +            sleep(Duration::from_millis(10));
> > +        });
> > +    }
> > +
> 
> Benefit of the background thread?

Again copied from cxx, I think the idea here is to get the events one by one and
read one before the next one is generated. i.e. to do it all in parallel, which
looked fine to me.

> > +    mod properties {
> > +        use super::*;
> > +
> > +        #[test]
> > +        fn verify() {
> > +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> > +            sim.set_line_name(1, "one").unwrap();
> > +            sim.set_line_name(2, "two").unwrap();
> > +            sim.set_line_name(4, "four").unwrap();
> > +            sim.set_line_name(5, "five").unwrap();
> > +            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
> > +                .unwrap();
> > +            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
> > +                .unwrap();
> > +            sim.enable().unwrap();
> > +
> > +            let chip = Chip::open(sim.dev_path()).unwrap();
> > +            chip.line_info(6).unwrap();
> > +
> > +            let info4 = chip.line_info(4).unwrap();
> > +            assert_eq!(info4.get_offset(), 4);
> > +            assert_eq!(info4.get_name().unwrap(), "four");
> > +            assert_eq!(info4.is_used(), true);
> > +            assert_eq!(info4.get_consumer().unwrap(), "hog4");
> > +            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
> > +            assert_eq!(info4.is_active_low(), false);
> > +            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
> > +            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
> > +            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
> > +            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
> > +            assert_eq!(info4.is_debounced(), false);
> > +            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
> > +        }
> > +    }
> 
> Test that you can read all supported values for all fields.

Like setting bias to all possible values one by one and reading them back ? Or
something else ?

> Clippy warnings to fix:
> 
> $cargo clippy --tests

I just ran

cargo clippy --workspace --bins --examples --benches --all-features -- -D warnings

and thought it works for tests too, my bad.
Kent Gibson July 27, 2022, 10:27 a.m. UTC | #3
On Wed, Jul 27, 2022 at 03:29:55PM +0530, Viresh Kumar wrote:
> On 27-07-22, 10:58, Kent Gibson wrote:
> > On Fri, Jul 08, 2022 at 05:05:00PM +0530, Viresh Kumar wrote:
> > Don't include the test results in the commit message.
> > Those are more of a release artifact than a commit artifact.
> > It is assumed the tests pass for you or you wouldn't be submitting them.
> 
> I wasn't trying to prove that I tested them :)
> 
> The idea was to show how module/test names eventually look in the output.
> 
> Maybe I could have just replied to this email and pasted it, sure commit message
> doesn't need it.
> 

If you want to add more detail to the patch, but not to the commit
message, then add it between the "---" and the file list.
Or put it in the cover letter.

> > > diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs
> > > +    mod configure {
> > > +        use super::*;
> > > +        const NGPIO: u64 = 16;
> > > +        const LABEL: &str = "foobar";
> > > +
> > > +        #[test]
> > > +        fn verify() {
> > > +            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
> > > +            let chip = Chip::open(sim.dev_path()).unwrap();
> > > +
> > > +            assert_eq!(chip.get_label().unwrap(), LABEL);
> > > +            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
> > > +            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
> > > +            assert_eq!(chip.get_num_lines(), NGPIO as u32);
> > > +            chip.get_fd().unwrap();
> > > +        }
> > > +
> > 
> > A test for a basic open on an existing chip and a smoke test of some
> > chip methods is called "verify", so chip::configure::verify?
> 
> You want me to rename this ? Sorry, got confused :(

I was trying to work out your naming scheme.
Couldn't see one - sorry.

> 
> Yeah, I am generally bad with naming, suggestions are welcome here :)
> 

Naming is always the hard part.  Find a system that will do the worst of
it for you.

I usually go with <struct>::<method> for the tests that cover a method
on a struct.  For complex methods that require multiple tests add
another tier that described what subset of functionality or failure mode
is being tested.

> > > +        #[test]
> > > +        fn line_lookup() {
> > > +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> > > +            sim.set_line_name(0, "zero").unwrap();
> > > +            sim.set_line_name(2, "two").unwrap();
> > > +            sim.set_line_name(3, "three").unwrap();
> > > +            sim.set_line_name(5, "five").unwrap();
> > > +            sim.set_line_name(10, "ten").unwrap();
> > > +            sim.set_line_name(11, "ten").unwrap();
> > > +            sim.enable().unwrap();
> > > +
> > > +            let chip = Chip::open(sim.dev_path()).unwrap();
> > > +
> > > +            // Success case
> > > +            assert_eq!(chip.find_line("zero").unwrap(), 0);
> > > +            assert_eq!(chip.find_line("two").unwrap(), 2);
> > > +            assert_eq!(chip.find_line("three").unwrap(), 3);
> > > +            assert_eq!(chip.find_line("five").unwrap(), 5);
> > > +
> > > +            // Success with duplicate names, should return first entry
> > > +            assert_eq!(chip.find_line("ten").unwrap(), 10);
> > > +
> > > +            // Failure
> > > +            assert_eq!(
> > > +                chip.find_line("nonexistent").unwrap_err(),
> > > +                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
> > > +            );
> > > +        }
> > 
> > If it is testing find_line() then why not call it find_line?
> 
> I think I picked many names from the TEST_CASE name from cxx bindings. This was
> written there as:
> 
> TEST_CASE("line lookup by name works", "[chip]")
> 
> and I didn't think much about it :)
> 

Fair enough.

> Sure I can name this find_line().
> 

At least until you get around to renaming find_line to line_offset_from_name ;-).

> > > diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs
> > > +        #[test]
> > > +        fn wait_timeout() {
> > > +            let mut config = TestConfig::new(NGPIO).unwrap();
> > > +            config.rconfig(Some(&[0]));
> > > +            config.lconfig_edge(Some(Edge::Both));
> > > +            config.request_lines().unwrap();
> > > +
> > > +            // No events available
> > > +            assert_eq!(
> > > +                config
> > > +                    .request()
> > > +                    .wait_edge_event(Duration::from_millis(100))
> > > +                    .unwrap_err(),
> > > +                ChipError::OperationTimedOut
> > > +            );
> > > +        }
> > 
> > Is a timeout really a "failure"?
> > 
> > It is testing wait_edge_event(), which is a method of line_request,
> > and so should be in the line_request test suite.
> 
> Copied it from cxx again, I just tried to add similar tests in similar files.
> 
> TEST_CASE("edge_event wait timeout", "[edge-event]")
> 
> > > +
> > > +        #[test]
> > > +        fn dir_out_edge_failure() {
> > > +            let mut config = TestConfig::new(NGPIO).unwrap();
> > > +            config.rconfig(Some(&[0]));
> > > +            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
> > > +
> > > +            assert_eq!(
> > > +                config.request_lines().unwrap_err(),
> > > +                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
> > > +            );
> > > +        }
> > > +    }
> > > +
> > 
> > This is testing a failure mode of request_lines(), not edge_events.
> > Where is the edge_event here?
> 
> I agree, I will move this out.
> 
> This needs fixing too though.
> 
> TEST_CASE("output mode and edge detection don't work together", "[edge-event]")
> 
> > > diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs
> > > new file mode 100644
> > > index 000000000000..96d8385deadf
> > > --- /dev/null
> > > +++ b/bindings/rust/tests/info_event.rs
> > > @@ -0,0 +1,126 @@
> > > +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> > > +//
> > > +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> > > +//     Viresh Kumar <viresh.kumar@linaro.org>
> > > +
> > > +mod common;
> > > +
> > > +mod info_event {
> > > +    use libc::EINVAL;
> > > +    use std::sync::Arc;
> > > +    use std::thread::{sleep, spawn};
> > > +    use std::time::Duration;
> > > +
> > > +    use vmm_sys_util::errno::Error as IoError;
> > > +
> > > +    use crate::common::*;
> > > +    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
> > > +
> > > +    fn request_reconfigure_line(chip: Arc<Chip>) {
> > > +        spawn(move || {
> > > +            sleep(Duration::from_millis(10));
> > > +
> > > +            let lconfig1 = LineConfig::new().unwrap();
> > > +            let rconfig = RequestConfig::new().unwrap();
> > > +            rconfig.set_offsets(&[7]);
> > > +
> > > +            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
> > > +
> > > +            sleep(Duration::from_millis(10));
> > > +
> > > +            let mut lconfig2 = LineConfig::new().unwrap();
> > > +            lconfig2.set_direction_default(Direction::Output);
> > > +
> > > +            request.reconfigure_lines(&lconfig2).unwrap();
> > > +
> > > +            sleep(Duration::from_millis(10));
> > > +        });
> > > +    }
> > > +
> > 
> > Benefit of the background thread?
> 
> Again copied from cxx, I think the idea here is to get the events one by one and
> read one before the next one is generated. i.e. to do it all in parallel, which
> looked fine to me.
> 

Following the pattern from the other bindings is fine.

That is a point where Bart and I disagree.
He likes making the tests closer to reality.
I like keeping them simple.

They are separate paths and strictly speaking both should be tested.
But if you are only going to do one, do the easy one ;-).

Anyway leave it as is - no point changing it now.

> > > +    mod properties {
> > > +        use super::*;
> > > +
> > > +        #[test]
> > > +        fn verify() {
> > > +            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
> > > +            sim.set_line_name(1, "one").unwrap();
> > > +            sim.set_line_name(2, "two").unwrap();
> > > +            sim.set_line_name(4, "four").unwrap();
> > > +            sim.set_line_name(5, "five").unwrap();
> > > +            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
> > > +                .unwrap();
> > > +            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
> > > +                .unwrap();
> > > +            sim.enable().unwrap();
> > > +
> > > +            let chip = Chip::open(sim.dev_path()).unwrap();
> > > +            chip.line_info(6).unwrap();
> > > +
> > > +            let info4 = chip.line_info(4).unwrap();
> > > +            assert_eq!(info4.get_offset(), 4);
> > > +            assert_eq!(info4.get_name().unwrap(), "four");
> > > +            assert_eq!(info4.is_used(), true);
> > > +            assert_eq!(info4.get_consumer().unwrap(), "hog4");
> > > +            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
> > > +            assert_eq!(info4.is_active_low(), false);
> > > +            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
> > > +            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
> > > +            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
> > > +            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
> > > +            assert_eq!(info4.is_debounced(), false);
> > > +            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
> > > +        }
> > > +    }
> > 
> > Test that you can read all supported values for all fields.
> 
> Like setting bias to all possible values one by one and reading them back ? Or
> something else ?
> 
> > Clippy warnings to fix:
> > 
> > $cargo clippy --tests
> 
> I just ran
> 
> cargo clippy --workspace --bins --examples --benches --all-features -- -D warnings
> 
> and thought it works for tests too, my bad.
> 

Turns out not.  Surprised me too.

Cheers,
Kent.
Viresh Kumar Aug. 1, 2022, 11:54 a.m. UTC | #4
On 27-07-22, 10:58, Kent Gibson wrote:
> > diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs
> > +        #[test]
> > +        fn set_bias() {
> > +            let offsets = [3];
> > +            let mut config = TestConfig::new(NGPIO).unwrap();
> > +            config.rconfig(Some(&offsets));
> > +            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
> > +            config.request_lines().unwrap();
> > +            config.request();
> > +
> > +            // Set single value
> > +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
> > +        }
> > +    }
> 
> Test reconfigure() failure modes.

What are we specifically looking to test here ? I am not sure I understood which
failures modes I should target.

> Test you can reconfigure all values for all attributes (not all values
> for debounce obviously - just the flags).

This I have done now, with request.reconfigure_lines().

> Similarly for request_lines (ideally in chip.rs)

Isn't this same as the above one? I am not sure again what I should test here.

I have pushed my current changes here, if it helps:

git@github.com:vireshk/libgpiod.git master
Kent Gibson Aug. 1, 2022, 12:38 p.m. UTC | #5
On Mon, Aug 01, 2022 at 05:24:02PM +0530, Viresh Kumar wrote:
> On 27-07-22, 10:58, Kent Gibson wrote:
> > > diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs
> > > +        #[test]
> > > +        fn set_bias() {
> > > +            let offsets = [3];
> > > +            let mut config = TestConfig::new(NGPIO).unwrap();
> > > +            config.rconfig(Some(&offsets));
> > > +            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
> > > +            config.request_lines().unwrap();
> > > +            config.request();
> > > +
> > > +            // Set single value
> > > +            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
> > > +        }
> > > +    }
> > 
> > Test reconfigure() failure modes.
> 
> What are we specifically looking to test here ? I am not sure I understood which
> failures modes I should target.
> 

reconfigure() can return E2BIG if the configuration is too complex to
encode into uAPI structures.  There are only 10 attribute slots in the uAPI
config, so more than 11 lines with distinct configs can't be encoded.
You want to test that you propagate that error correctly.
I typically test this by giving each line a different debounce value.

> > Test you can reconfigure all values for all attributes (not all values
> > for debounce obviously - just the flags).
> 
> This I have done now, with request.reconfigure_lines().
> 
> > Similarly for request_lines (ideally in chip.rs)
> 
> Isn't this same as the above one? I am not sure again what I should test here.
> 

The request_lines() could be considered sufficient to test
the conversions for both request_config and line_config objects.

But testing request_lines() and reconfigure_lines() doesn't just test
those conversions, it checks that the functions don't mess with the
config along the way.
If you want to take that as a given then sure, just testing the
config variants for request_lines() will do.

Cheers,
Kent.

> I have pushed my current changes here, if it helps:
> 
> git@github.com:vireshk/libgpiod.git master
> 
> -- 
> viresh
Viresh Kumar Aug. 2, 2022, 5:44 a.m. UTC | #6
On 01-08-22, 20:38, Kent Gibson wrote:
> The request_lines() could be considered sufficient to test
> the conversions for both request_config and line_config objects.
> 
> But testing request_lines() and reconfigure_lines() doesn't just test
> those conversions, it checks that the functions don't mess with the
> config along the way.
> If you want to take that as a given then sure, just testing the
> config variants for request_lines() will do.

Okay, so we want test setting various flag types in line_config and
then see if request_line() fails or not and verify (read) them later.
Right ?

Very similar to this is also done in tests/line_info now, where you
suggested:

"Test that you can read all supported values for all fields."

So I set all possible values for fields and then read them back.

Lets see after the next version, if they are sufficient or not. I will
happily add more if required then.

Thanks.
Kent Gibson Aug. 2, 2022, 5:47 a.m. UTC | #7
On Tue, Aug 02, 2022 at 11:14:34AM +0530, Viresh Kumar wrote:
> On 01-08-22, 20:38, Kent Gibson wrote:
> > The request_lines() could be considered sufficient to test
> > the conversions for both request_config and line_config objects.
> > 
> > But testing request_lines() and reconfigure_lines() doesn't just test
> > those conversions, it checks that the functions don't mess with the
> > config along the way.
> > If you want to take that as a given then sure, just testing the
> > config variants for request_lines() will do.
> 
> Okay, so we want test setting various flag types in line_config and
> then see if request_line() fails or not and verify (read) them later.
> Right ?
> 

Right.

Cheers,
Kent.

> Very similar to this is also done in tests/line_info now, where you
> suggested:
> 
> "Test that you can read all supported values for all fields."
> 
> So I set all possible values for fields and then read them back.
> 
> Lets see after the next version, if they are sufficient or not. I will
> happily add more if required then.
> 
> Thanks.
> 
> -- 
> viresh
diff mbox series

Patch

diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml
index d5d81486fa2f..e2d6d5bb91b6 100644
--- a/bindings/rust/Cargo.toml
+++ b/bindings/rust/Cargo.toml
@@ -10,3 +10,6 @@  libc = ">=0.2.39"
 libgpiod-sys = { path = "libgpiod-sys" }
 thiserror = "1.0"
 vmm-sys-util = "=0.9.0"
+
+[dev-dependencies]
+libgpiod-sys = { path = "libgpiod-sys", features = ["gpiosim"] }
diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs
index bb338e72671d..c1dbbb397e73 100644
--- a/bindings/rust/src/line_request.rs
+++ b/bindings/rust/src/line_request.rs
@@ -15,6 +15,7 @@  use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestC
 /// Line request operations
 ///
 /// Allows interaction with a set of requested lines.
+#[derive(Debug)]
 pub struct LineRequest {
     request: *mut bindings::gpiod_line_request,
 }
diff --git a/bindings/rust/tests/chip.rs b/bindings/rust/tests/chip.rs
new file mode 100644
index 000000000000..4e64e9c7e291
--- /dev/null
+++ b/bindings/rust/tests/chip.rs
@@ -0,0 +1,96 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod chip {
+    use libc::{ENODEV, ENOENT, ENOTTY};
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Chip, Error as ChipError};
+
+    mod create {
+        use super::*;
+
+        #[test]
+        fn nonexistent_file_failure() {
+            assert_eq!(
+                Chip::open("/dev/nonexistent").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOENT))
+            );
+        }
+
+        #[test]
+        fn no_dev_file_failure() {
+            assert_eq!(
+                Chip::open("/tmp").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENOTTY))
+            );
+        }
+
+        #[test]
+        fn non_gpio_char_dev_file_failure() {
+            assert_eq!(
+                Chip::open("/dev/null").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip open", IoError::new(ENODEV))
+            );
+        }
+
+        #[test]
+        fn existing() {
+            let sim = Sim::new(None, None, true).unwrap();
+            Chip::open(sim.dev_path()).unwrap();
+        }
+    }
+
+    mod configure {
+        use super::*;
+        const NGPIO: u64 = 16;
+        const LABEL: &str = "foobar";
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            assert_eq!(chip.get_label().unwrap(), LABEL);
+            assert_eq!(chip.get_name().unwrap(), sim.chip_name());
+            assert_eq!(chip.get_path().unwrap(), sim.dev_path());
+            assert_eq!(chip.get_num_lines(), NGPIO as u32);
+            chip.get_fd().unwrap();
+        }
+
+        #[test]
+        fn line_lookup() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(0, "zero").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(3, "three").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.set_line_name(10, "ten").unwrap();
+            sim.set_line_name(11, "ten").unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            // Success case
+            assert_eq!(chip.find_line("zero").unwrap(), 0);
+            assert_eq!(chip.find_line("two").unwrap(), 2);
+            assert_eq!(chip.find_line("three").unwrap(), 3);
+            assert_eq!(chip.find_line("five").unwrap(), 5);
+
+            // Success with duplicate names, should return first entry
+            assert_eq!(chip.find_line("ten").unwrap(), 10);
+
+            // Failure
+            assert_eq!(
+                chip.find_line("nonexistent").unwrap_err(),
+                ChipError::OperationFailed("Gpio Chip find-line", IoError::new(ENOENT))
+            );
+        }
+    }
+}
diff --git a/bindings/rust/tests/common/config.rs b/bindings/rust/tests/common/config.rs
new file mode 100644
index 000000000000..3abd9a8c4c8b
--- /dev/null
+++ b/bindings/rust/tests/common/config.rs
@@ -0,0 +1,117 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::sync::Arc;
+
+use crate::common::*;
+
+use libgpiod::{Bias, Chip, Direction, Edge, LineConfig, LineRequest, RequestConfig, Result};
+
+//#[derive(Debug)]
+pub(crate) struct TestConfig {
+    sim: Arc<Sim>,
+    chip: Option<Chip>,
+    request: Option<LineRequest>,
+    rconfig: RequestConfig,
+    lconfig: LineConfig,
+}
+
+impl TestConfig {
+    pub(crate) fn new(ngpio: u64) -> Result<Self> {
+        Ok(Self {
+            sim: Arc::new(Sim::new(Some(ngpio), None, true)?),
+            chip: None,
+            request: None,
+            rconfig: RequestConfig::new().unwrap(),
+            lconfig: LineConfig::new().unwrap(),
+        })
+    }
+
+    pub(crate) fn set_pull(&self, offsets: &[u32], pulls: &[u32]) {
+        for i in 0..pulls.len() {
+            self.sim.set_pull(offsets[i], pulls[i] as i32).unwrap();
+        }
+    }
+
+    pub(crate) fn rconfig_consumer(&self, offsets: Option<&[u32]>, consumer: Option<&str>) {
+        if let Some(offsets) = offsets {
+            self.rconfig.set_offsets(offsets);
+        }
+
+        if let Some(consumer) = consumer {
+            self.rconfig.set_consumer(consumer);
+        }
+    }
+
+    pub(crate) fn rconfig(&self, offsets: Option<&[u32]>) {
+        self.rconfig_consumer(offsets, None);
+    }
+
+    pub(crate) fn lconfig(
+        &mut self,
+        dir: Option<Direction>,
+        val: Option<u32>,
+        val_override: Option<(u32, u32)>,
+        edge: Option<Edge>,
+        bias: Option<Bias>,
+    ) {
+        if let Some(bias) = bias {
+            self.lconfig.set_bias_default(bias);
+        }
+
+        if let Some(edge) = edge {
+            self.lconfig.set_edge_detection_default(edge);
+        }
+
+        if let Some(dir) = dir {
+            self.lconfig.set_direction_default(dir);
+        }
+
+        if let Some(val) = val {
+            self.lconfig.set_output_value_default(val);
+        }
+
+        if let Some((offset, val)) = val_override {
+            self.lconfig.set_output_value_override(val, offset);
+        }
+    }
+
+    pub(crate) fn lconfig_raw(&mut self) {
+        self.lconfig(None, None, None, None, None);
+    }
+
+    pub(crate) fn lconfig_edge(&mut self, edge: Option<Edge>) {
+        self.lconfig(None, None, None, edge, None);
+    }
+
+    pub(crate) fn request_lines(&mut self) -> Result<()> {
+        let chip = Chip::open(self.sim.dev_path())?;
+
+        self.request = Some(chip.request_lines(&self.rconfig, &self.lconfig)?);
+        self.chip = Some(chip);
+
+        Ok(())
+    }
+
+    pub(crate) fn sim(&self) -> Arc<Sim> {
+        self.sim.clone()
+    }
+
+    pub(crate) fn chip(&self) -> &Chip {
+        &self.chip.as_ref().unwrap()
+    }
+
+    pub(crate) fn request(&self) -> &LineRequest {
+        &self.request.as_ref().unwrap()
+    }
+}
+
+impl Drop for TestConfig {
+    fn drop(&mut self) {
+        // Explicit freeing is important to make sure "request" get freed
+        // before "sim" and "chip".
+        self.request = None;
+    }
+}
diff --git a/bindings/rust/tests/common/mod.rs b/bindings/rust/tests/common/mod.rs
new file mode 100644
index 000000000000..2dc37986396b
--- /dev/null
+++ b/bindings/rust/tests/common/mod.rs
@@ -0,0 +1,16 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+#[allow(dead_code)]
+mod sim;
+
+#[allow(unused_imports)]
+pub(crate) use sim::*;
+
+#[allow(dead_code)]
+mod config;
+
+#[allow(unused_imports)]
+pub(crate) use config::*;
diff --git a/bindings/rust/tests/common/sim.rs b/bindings/rust/tests/common/sim.rs
new file mode 100644
index 000000000000..cd5ec66c3da5
--- /dev/null
+++ b/bindings/rust/tests/common/sim.rs
@@ -0,0 +1,306 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::os::raw::c_char;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use libgpiod::{Error, Result};
+use libgpiod_sys as bindings;
+
+/// Sim Ctx
+#[derive(Debug)]
+struct SimCtx {
+    ctx: *mut bindings::gpiosim_ctx,
+}
+
+unsafe impl Send for SimCtx {}
+unsafe impl Sync for SimCtx {}
+
+impl SimCtx {
+    fn new() -> Result<Self> {
+        let ctx = unsafe { bindings::gpiosim_ctx_new() };
+        if ctx.is_null() {
+            return Err(Error::OperationFailed("gpio-sim ctx new", IoError::last()));
+        }
+
+        Ok(Self { ctx })
+    }
+
+    fn ctx(&self) -> *mut bindings::gpiosim_ctx {
+        self.ctx
+    }
+}
+
+impl Drop for SimCtx {
+    fn drop(&mut self) {
+        unsafe { bindings::gpiosim_ctx_unref(self.ctx) }
+    }
+}
+
+/// Sim Dev
+#[derive(Debug)]
+struct SimDev {
+    dev: *mut bindings::gpiosim_dev,
+}
+
+unsafe impl Send for SimDev {}
+unsafe impl Sync for SimDev {}
+
+impl SimDev {
+    fn new(ctx: &SimCtx) -> Result<Self> {
+        let dev = unsafe { bindings::gpiosim_dev_new(ctx.ctx()) };
+        if dev.is_null() {
+            return Err(Error::OperationFailed("gpio-sim dev new", IoError::last()));
+        }
+
+        Ok(Self { dev })
+    }
+
+    fn dev(&self) -> *mut bindings::gpiosim_dev {
+        self.dev
+    }
+
+    fn enable(&self) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_dev_enable(self.dev) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim dev-enable",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn disable(&self) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_dev_disable(self.dev) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim dev-disable",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+}
+
+impl Drop for SimDev {
+    fn drop(&mut self) {
+        unsafe { bindings::gpiosim_dev_unref(self.dev) }
+    }
+}
+
+/// Sim Bank
+#[derive(Debug)]
+struct SimBank {
+    bank: *mut bindings::gpiosim_bank,
+}
+
+unsafe impl Send for SimBank {}
+unsafe impl Sync for SimBank {}
+
+impl SimBank {
+    fn new(dev: &SimDev) -> Result<Self> {
+        let bank = unsafe { bindings::gpiosim_bank_new(dev.dev()) };
+        if bank.is_null() {
+            return Err(Error::OperationFailed("gpio-sim Bank new", IoError::last()));
+        }
+
+        Ok(Self { bank })
+    }
+
+    fn chip_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by gpiosim is guaranteed to live as long
+        // as the `struct SimBank`.
+        let name = unsafe { bindings::gpiosim_bank_get_chip_name(self.bank) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    fn dev_path(&self) -> Result<&str> {
+        // SAFETY: The string returned by gpiosim is guaranteed to live as long
+        // as the `struct SimBank`.
+        let path = unsafe { bindings::gpiosim_bank_get_dev_path(self.bank) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(path as *const u8, bindings::strlen(path) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    fn val(&self, offset: u32) -> Result<u32> {
+        let ret = unsafe { bindings::gpiosim_bank_get_value(self.bank, offset) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim get-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(ret as u32)
+        }
+    }
+
+    fn set_label(&self, label: &str) -> Result<()> {
+        // Null-terminate the string
+        let label = label.to_owned() + "\0";
+
+        let ret =
+            unsafe { bindings::gpiosim_bank_set_label(self.bank, label.as_ptr() as *const c_char) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim set-label",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn set_num_lines(&self, num: u64) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_bank_set_num_lines(self.bank, num) };
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim set-num-lines",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn set_line_name(&self, offset: u32, name: &str) -> Result<()> {
+        // Null-terminate the string
+        let name = name.to_owned() + "\0";
+
+        let ret = unsafe {
+            bindings::gpiosim_bank_set_line_name(self.bank, offset, name.as_ptr() as *const c_char)
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "gpio-sim set-line-name",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn set_pull(&self, offset: u32, pull: i32) -> Result<()> {
+        let ret = unsafe { bindings::gpiosim_bank_set_pull(self.bank, offset, pull) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed("gpio-sim set-pull", IoError::last()))
+        } else {
+            Ok(())
+        }
+    }
+
+    fn hog_line(&self, offset: u32, name: &str, dir: i32) -> Result<()> {
+        // Null-terminate the string
+        let name = name.to_owned() + "\0";
+
+        let ret = unsafe {
+            bindings::gpiosim_bank_hog_line(self.bank, offset, name.as_ptr() as *const c_char, dir)
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed("gpio-sim hog-line", IoError::last()))
+        } else {
+            Ok(())
+        }
+    }
+}
+
+impl Drop for SimBank {
+    fn drop(&mut self) {
+        unsafe { bindings::gpiosim_bank_unref(self.bank) }
+    }
+}
+
+/// GPIO SIM
+#[derive(Debug)]
+pub(crate) struct Sim {
+    ctx: SimCtx,
+    dev: SimDev,
+    bank: SimBank,
+}
+
+unsafe impl Send for Sim {}
+unsafe impl Sync for Sim {}
+
+impl Sim {
+    pub(crate) fn new(ngpio: Option<u64>, label: Option<&str>, enable: bool) -> Result<Self> {
+        let ctx = SimCtx::new()?;
+        let dev = SimDev::new(&ctx)?;
+        let bank = SimBank::new(&dev)?;
+
+        if let Some(ngpio) = ngpio {
+            bank.set_num_lines(ngpio)?;
+        }
+
+        if let Some(label) = label {
+            bank.set_label(label)?;
+        }
+
+        if enable {
+            dev.enable()?;
+        }
+
+        Ok(Self { ctx, dev, bank })
+    }
+
+    pub(crate) fn chip_name(&self) -> &str {
+        self.bank.chip_name().unwrap()
+    }
+
+    pub fn dev_path(&self) -> &str {
+        self.bank.dev_path().unwrap()
+    }
+
+    pub(crate) fn val(&self, offset: u32) -> Result<u32> {
+        self.bank.val(offset)
+    }
+
+    pub(crate) fn set_label(&self, label: &str) -> Result<()> {
+        self.bank.set_label(label)
+    }
+
+    pub(crate) fn set_num_lines(&self, num: u64) -> Result<()> {
+        self.bank.set_num_lines(num)
+    }
+
+    pub(crate) fn set_line_name(&self, offset: u32, name: &str) -> Result<()> {
+        self.bank.set_line_name(offset, name)
+    }
+
+    pub(crate) fn set_pull(&self, offset: u32, pull: i32) -> Result<()> {
+        self.bank.set_pull(offset, pull)
+    }
+
+    pub(crate) fn hog_line(&self, offset: u32, name: &str, dir: i32) -> Result<()> {
+        self.bank.hog_line(offset, name, dir)
+    }
+
+    pub(crate) fn enable(&self) -> Result<()> {
+        self.dev.enable()
+    }
+
+    pub(crate) fn disable(&self) -> Result<()> {
+        self.dev.disable()
+    }
+}
diff --git a/bindings/rust/tests/edge_event.rs b/bindings/rust/tests/edge_event.rs
new file mode 100644
index 000000000000..1b05b225aab7
--- /dev/null
+++ b/bindings/rust/tests/edge_event.rs
@@ -0,0 +1,389 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod edge_event {
+    use libc::EINVAL;
+    use std::sync::Arc;
+    use std::thread::{sleep, spawn};
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Direction, Edge, EdgeEventBuffer, Error as ChipError, LineEdgeEvent};
+    use libgpiod_sys::{GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP};
+
+    const NGPIO: u64 = 8;
+
+    mod buffer_settings {
+        use super::*;
+
+        #[test]
+        fn default_capacity() {
+            assert_eq!(EdgeEventBuffer::new(0).unwrap().get_capacity(), 64);
+        }
+
+        #[test]
+        fn user_defined_capacity() {
+            assert_eq!(EdgeEventBuffer::new(123).unwrap().get_capacity(), 123);
+        }
+
+        #[test]
+        fn max_capacity() {
+            assert_eq!(EdgeEventBuffer::new(1024 * 2).unwrap().get_capacity(), 1024);
+        }
+    }
+
+    mod failure {
+        use super::*;
+
+        #[test]
+        fn wait_timeout() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn dir_out_edge_failure() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[0]));
+            config.lconfig(Some(Direction::Output), None, None, Some(Edge::Both), None);
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+    }
+
+    mod verify {
+        use super::*;
+
+        // Helpers to generate events
+        fn trigger_falling_and_rising_edge(sim: Arc<Sim>, offset: u32) {
+            spawn(move || {
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
+            });
+        }
+
+        fn trigger_rising_edge_events_on_two_offsets(sim: Arc<Sim>, offset: [u32; 2]) {
+            spawn(move || {
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset[0], GPIOSIM_PULL_UP as i32).unwrap();
+
+                sleep(Duration::from_millis(30));
+                sim.set_pull(offset[1], GPIOSIM_PULL_UP as i32).unwrap();
+            });
+        }
+
+        fn trigger_multiple_events(sim: Arc<Sim>, offset: u32) {
+            sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+            sleep(Duration::from_millis(10));
+
+            sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
+            sleep(Duration::from_millis(10));
+
+            sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+            sleep(Duration::from_millis(10));
+        }
+
+        #[test]
+        fn both_edges() {
+            const GPIO: u32 = 2;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+            // Rising event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            let ts_rising = event.get_timestamp();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // Falling event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            let ts_falling = event.get_timestamp();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Falling);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+
+            assert!(ts_falling > ts_rising);
+        }
+
+        #[test]
+        fn rising_edge() {
+            const GPIO: u32 = 6;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Rising));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+            // Rising event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn falling_edge() {
+            const GPIO: u32 = 7;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Falling));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+            // Falling event
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Falling);
+            assert_eq!(event.get_line_offset(), GPIO);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn edge_sequence() {
+            const GPIO: [u32; 2] = [0, 1];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&GPIO));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO);
+
+            // Rising event GPIO 0
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO[0]);
+            assert_eq!(event.get_global_seqno(), 1);
+            assert_eq!(event.get_line_seqno(), 1);
+
+            // Rising event GPIO 1
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                1
+            );
+            assert_eq!(buf.get_num_events(), 1);
+
+            let event = buf.get_event(0).unwrap();
+            assert_eq!(event.get_event_type().unwrap(), LineEdgeEvent::Rising);
+            assert_eq!(event.get_line_offset(), GPIO[1]);
+            assert_eq!(event.get_global_seqno(), 2);
+            assert_eq!(event.get_line_seqno(), 1);
+
+            // No events available
+            assert_eq!(
+                config
+                    .request()
+                    .wait_edge_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn multiple_events() {
+            const GPIO: u32 = 1;
+            let buf = EdgeEventBuffer::new(0).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_multiple_events(config.sim(), GPIO);
+
+            // Read multiple events
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                3
+            );
+            assert_eq!(buf.get_num_events(), 3);
+
+            let mut global_seqno = 1;
+            let mut line_seqno = 1;
+
+            // Verify sequence number of events
+            for i in 0..3 {
+                let event = buf.get_event(i).unwrap();
+                assert_eq!(event.get_line_offset(), GPIO);
+                assert_eq!(event.get_global_seqno(), global_seqno);
+                assert_eq!(event.get_line_seqno(), line_seqno);
+
+                global_seqno += 1;
+                line_seqno += 1;
+            }
+        }
+
+        #[test]
+        fn over_capacity() {
+            const GPIO: u32 = 2;
+            let buf = EdgeEventBuffer::new(2).unwrap();
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_edge(Some(Edge::Both));
+            config.request_lines().unwrap();
+
+            // Generate events
+            trigger_multiple_events(config.sim(), GPIO);
+
+            // Read multiple events
+            config
+                .request()
+                .wait_edge_event(Duration::from_secs(1))
+                .unwrap();
+
+            assert_eq!(
+                config
+                    .request()
+                    .read_edge_event(&buf, buf.get_capacity())
+                    .unwrap(),
+                2
+            );
+            assert_eq!(buf.get_num_events(), 2);
+        }
+    }
+}
diff --git a/bindings/rust/tests/info_event.rs b/bindings/rust/tests/info_event.rs
new file mode 100644
index 000000000000..96d8385deadf
--- /dev/null
+++ b/bindings/rust/tests/info_event.rs
@@ -0,0 +1,126 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod info_event {
+    use libc::EINVAL;
+    use std::sync::Arc;
+    use std::thread::{sleep, spawn};
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Chip, Direction, Error as ChipError, Event, LineConfig, RequestConfig};
+
+    fn request_reconfigure_line(chip: Arc<Chip>) {
+        spawn(move || {
+            sleep(Duration::from_millis(10));
+
+            let lconfig1 = LineConfig::new().unwrap();
+            let rconfig = RequestConfig::new().unwrap();
+            rconfig.set_offsets(&[7]);
+
+            let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
+
+            sleep(Duration::from_millis(10));
+
+            let mut lconfig2 = LineConfig::new().unwrap();
+            lconfig2.set_direction_default(Direction::Output);
+
+            request.reconfigure_lines(&lconfig2).unwrap();
+
+            sleep(Duration::from_millis(10));
+        });
+    }
+
+    mod watch {
+        use super::*;
+        const NGPIO: u64 = 8;
+        const GPIO: u32 = 7;
+
+        #[test]
+        fn failure() {
+            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+
+            assert_eq!(
+                chip.watch_line_info(NGPIO as u32).unwrap_err(),
+                ChipError::OperationFailed("Gpio LineInfo line-info", IoError::new(EINVAL))
+            );
+
+            chip.watch_line_info(3).unwrap();
+
+            // No events available
+            assert_eq!(
+                chip.wait_info_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+        }
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            let info = chip.watch_line_info(GPIO).unwrap();
+
+            assert_eq!(info.get_offset(), GPIO);
+        }
+
+        #[test]
+        fn reconfigure() {
+            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+            let chip = Arc::new(Chip::open(sim.dev_path()).unwrap());
+            let info = chip.watch_line_info(GPIO).unwrap();
+
+            assert_eq!(info.get_direction().unwrap(), Direction::Input);
+
+            // Generate events
+            request_reconfigure_line(chip.clone());
+
+            // Line requested event
+            chip.wait_info_event(Duration::from_secs(1)).unwrap();
+            let event = chip.read_info_event().unwrap();
+            let ts_req = event.get_timestamp();
+
+            assert_eq!(event.get_event_type().unwrap(), Event::LineRequested);
+            assert_eq!(
+                event.line_info().unwrap().get_direction().unwrap(),
+                Direction::Input
+            );
+
+            // Line changed event
+            chip.wait_info_event(Duration::from_secs(1)).unwrap();
+            let event = chip.read_info_event().unwrap();
+            let ts_rec = event.get_timestamp();
+
+            assert_eq!(event.get_event_type().unwrap(), Event::LineConfigChanged);
+            assert_eq!(
+                event.line_info().unwrap().get_direction().unwrap(),
+                Direction::Output
+            );
+
+            // Line released event
+            chip.wait_info_event(Duration::from_secs(1)).unwrap();
+            let event = chip.read_info_event().unwrap();
+            let ts_rel = event.get_timestamp();
+
+            assert_eq!(event.get_event_type().unwrap(), Event::LineReleased);
+
+            // No events available
+            assert_eq!(
+                chip.wait_info_event(Duration::from_millis(100))
+                    .unwrap_err(),
+                ChipError::OperationTimedOut
+            );
+
+            // Check timestamps are really monotonic.
+            assert!(ts_rel > ts_rec);
+            assert!(ts_rec > ts_req);
+        }
+    }
+}
diff --git a/bindings/rust/tests/line_config.rs b/bindings/rust/tests/line_config.rs
new file mode 100644
index 000000000000..82879324a7f0
--- /dev/null
+++ b/bindings/rust/tests/line_config.rs
@@ -0,0 +1,187 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_config {
+    use std::time::Duration;
+
+    use libgpiod::{Bias, Direction, Drive, Edge, EventClock, LineConfig};
+
+    mod default {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            let lconfig = LineConfig::new().unwrap();
+
+            assert_eq!(lconfig.get_direction_default().unwrap(), Direction::AsIs);
+            assert_eq!(lconfig.get_edge_detection_default().unwrap(), Edge::None);
+            assert_eq!(lconfig.get_bias_default().unwrap(), Bias::AsIs);
+            assert_eq!(lconfig.get_drive_default().unwrap(), Drive::PushPull);
+            assert_eq!(lconfig.get_active_low_default(), false);
+            assert_eq!(
+                lconfig.get_debounce_period_default().unwrap(),
+                Duration::from_millis(0)
+            );
+            assert_eq!(
+                lconfig.get_event_clock_default().unwrap(),
+                EventClock::Monotonic
+            );
+            assert_eq!(lconfig.get_output_value_default().unwrap(), 0);
+            assert_eq!(lconfig.get_overrides().unwrap().len(), 0);
+        }
+    }
+
+    mod overrides {
+        use super::*;
+
+        #[test]
+        fn direction() {
+            const GPIO: u32 = 0;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_direction_default(Direction::AsIs);
+            lconfig.set_direction_override(Direction::Input, GPIO);
+
+            assert_eq!(lconfig.direction_is_overridden(GPIO), true);
+            assert_eq!(
+                lconfig.get_direction_offset(GPIO).unwrap(),
+                Direction::Input
+            );
+
+            lconfig.clear_direction_override(GPIO);
+            assert_eq!(lconfig.direction_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_direction_offset(GPIO).unwrap(), Direction::AsIs);
+        }
+
+        #[test]
+        fn edge_detection() {
+            const GPIO: u32 = 1;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_edge_detection_default(Edge::None);
+            lconfig.set_edge_detection_override(Edge::Both, GPIO);
+
+            assert_eq!(lconfig.edge_detection_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_edge_detection_offset(GPIO).unwrap(), Edge::Both);
+
+            lconfig.clear_edge_detection_override(GPIO);
+            assert_eq!(lconfig.edge_detection_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_edge_detection_offset(GPIO).unwrap(), Edge::None);
+        }
+
+        #[test]
+        fn bias() {
+            const GPIO: u32 = 2;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_bias_default(Bias::AsIs);
+            lconfig.set_bias_override(Bias::PullDown, GPIO);
+
+            assert_eq!(lconfig.bias_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_bias_offset(GPIO).unwrap(), Bias::PullDown);
+
+            lconfig.clear_bias_override(GPIO);
+            assert_eq!(lconfig.bias_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_bias_offset(GPIO).unwrap(), Bias::AsIs);
+        }
+
+        #[test]
+        fn drive() {
+            const GPIO: u32 = 3;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_drive_default(Drive::PushPull);
+            lconfig.set_drive_override(Drive::OpenDrain, GPIO);
+
+            assert_eq!(lconfig.drive_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_drive_offset(GPIO).unwrap(), Drive::OpenDrain);
+
+            lconfig.clear_drive_override(GPIO);
+            assert_eq!(lconfig.drive_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_drive_offset(GPIO).unwrap(), Drive::PushPull);
+        }
+
+        #[test]
+        fn active_low() {
+            const GPIO: u32 = 4;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_active_low_default(false);
+            lconfig.set_active_low_override(true, GPIO);
+
+            assert_eq!(lconfig.active_low_is_overridden(GPIO), true);
+            assert_eq!(lconfig.get_active_low_offset(GPIO), true);
+
+            lconfig.clear_active_low_override(GPIO);
+            assert_eq!(lconfig.active_low_is_overridden(GPIO), false);
+            assert_eq!(lconfig.get_active_low_offset(GPIO), false);
+        }
+
+        #[test]
+        fn debounce_period() {
+            const GPIO: u32 = 5;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_debounce_period_default(Duration::from_millis(5));
+            lconfig.set_debounce_period_override(Duration::from_millis(3), GPIO);
+
+            assert_eq!(lconfig.debounce_period_is_overridden(GPIO), true);
+            assert_eq!(
+                lconfig.get_debounce_period_offset(GPIO).unwrap(),
+                Duration::from_millis(3)
+            );
+
+            lconfig.clear_debounce_period_override(GPIO);
+            assert_eq!(lconfig.debounce_period_is_overridden(GPIO), false);
+            assert_eq!(
+                lconfig.get_debounce_period_offset(GPIO).unwrap(),
+                Duration::from_millis(5)
+            );
+        }
+
+        #[test]
+        fn event_clock() {
+            const GPIO: u32 = 6;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_event_clock_default(EventClock::Monotonic);
+            lconfig.set_event_clock_override(EventClock::Realtime, GPIO);
+
+            assert_eq!(lconfig.event_clock_is_overridden(GPIO), true);
+            assert_eq!(
+                lconfig.get_event_clock_offset(GPIO).unwrap(),
+                EventClock::Realtime
+            );
+
+            lconfig.clear_event_clock_override(GPIO);
+            assert_eq!(lconfig.event_clock_is_overridden(GPIO), false);
+            assert_eq!(
+                lconfig.get_event_clock_offset(GPIO).unwrap(),
+                EventClock::Monotonic
+            );
+        }
+
+        #[test]
+        fn output_value() {
+            const GPIO: u32 = 0;
+            let mut lconfig = LineConfig::new().unwrap();
+
+            lconfig.set_output_value_default(0);
+            lconfig.set_output_value_override(1, GPIO);
+            lconfig.set_output_values(&[1, 2, 8], &[1, 1, 1]).unwrap();
+
+            for line in [0, 1, 2, 8] {
+                assert_eq!(lconfig.output_value_is_overridden(line), true);
+                assert_eq!(lconfig.get_output_value_offset(line).unwrap(), 1);
+
+                lconfig.clear_output_value_override(line);
+                assert_eq!(lconfig.output_value_is_overridden(line), false);
+                assert_eq!(lconfig.get_output_value_offset(line).unwrap(), 0);
+            }
+        }
+    }
+}
diff --git a/bindings/rust/tests/line_info.rs b/bindings/rust/tests/line_info.rs
new file mode 100644
index 000000000000..f6f8f592cc85
--- /dev/null
+++ b/bindings/rust/tests/line_info.rs
@@ -0,0 +1,90 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_info {
+    use libc::EINVAL;
+    use std::time::Duration;
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Bias, Chip, Direction, Drive, Edge, Error as ChipError, EventClock};
+    use libgpiod_sys::{GPIOSIM_HOG_DIR_OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_LOW};
+
+    const NGPIO: u64 = 8;
+
+    mod basic {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            const GPIO: u32 = 0;
+            const LABEL: &str = "foobar";
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(GPIO, LABEL).unwrap();
+            sim.hog_line(GPIO, "hog", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+                .unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            let info = chip.line_info(GPIO).unwrap();
+
+            assert_eq!(info.get_offset(), GPIO);
+            assert_eq!(info.get_name().unwrap(), LABEL);
+            assert_eq!(info.is_used(), true);
+            assert_eq!(info.get_consumer().unwrap(), "hog");
+            assert_eq!(info.get_direction().unwrap(), Direction::Output);
+            assert_eq!(info.is_active_low(), false);
+            assert_eq!(info.get_bias().unwrap(), Bias::Unknown);
+            assert_eq!(info.get_drive().unwrap(), Drive::PushPull);
+            assert_eq!(info.get_edge_detection().unwrap(), Edge::None);
+            assert_eq!(info.get_event_clock().unwrap(), EventClock::Monotonic);
+            assert_eq!(info.is_debounced(), false);
+            assert_eq!(info.get_debounce_period(), Duration::from_millis(0));
+
+            assert_eq!(
+                chip.line_info(NGPIO as u32).unwrap_err(),
+                ChipError::OperationFailed("Gpio LineInfo line-info", IoError::new(EINVAL))
+            );
+        }
+    }
+
+    mod properties {
+        use super::*;
+
+        #[test]
+        fn verify() {
+            let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+            sim.set_line_name(1, "one").unwrap();
+            sim.set_line_name(2, "two").unwrap();
+            sim.set_line_name(4, "four").unwrap();
+            sim.set_line_name(5, "five").unwrap();
+            sim.hog_line(3, "hog3", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+                .unwrap();
+            sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
+                .unwrap();
+            sim.enable().unwrap();
+
+            let chip = Chip::open(sim.dev_path()).unwrap();
+            chip.line_info(6).unwrap();
+
+            let info4 = chip.line_info(4).unwrap();
+            assert_eq!(info4.get_offset(), 4);
+            assert_eq!(info4.get_name().unwrap(), "four");
+            assert_eq!(info4.is_used(), true);
+            assert_eq!(info4.get_consumer().unwrap(), "hog4");
+            assert_eq!(info4.get_direction().unwrap(), Direction::Output);
+            assert_eq!(info4.is_active_low(), false);
+            assert_eq!(info4.get_bias().unwrap(), Bias::Unknown);
+            assert_eq!(info4.get_drive().unwrap(), Drive::PushPull);
+            assert_eq!(info4.get_edge_detection().unwrap(), Edge::None);
+            assert_eq!(info4.get_event_clock().unwrap(), EventClock::Monotonic);
+            assert_eq!(info4.is_debounced(), false);
+            assert_eq!(info4.get_debounce_period(), Duration::from_millis(0));
+        }
+    }
+}
diff --git a/bindings/rust/tests/line_request.rs b/bindings/rust/tests/line_request.rs
new file mode 100644
index 000000000000..361ee6318d2e
--- /dev/null
+++ b/bindings/rust/tests/line_request.rs
@@ -0,0 +1,234 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_request {
+    use libc::{EBUSY, EINVAL};
+
+    use vmm_sys_util::errno::Error as IoError;
+
+    use crate::common::*;
+    use libgpiod::{Bias, Direction, Error as ChipError, LineConfig};
+    use libgpiod_sys::{
+        GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP, GPIOSIM_VALUE_ACTIVE, GPIOSIM_VALUE_INACTIVE,
+    };
+
+    const NGPIO: u64 = 8;
+
+    mod invalid_arguments {
+        use super::*;
+
+        #[test]
+        fn no_offsets() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(None);
+            config.lconfig_raw();
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+
+        #[test]
+        fn duplicate_offsets() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[2, 0, 0, 4]));
+            config.lconfig_raw();
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EBUSY))
+            );
+        }
+
+        #[test]
+        fn out_of_bound_offsets() {
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[2, 0, 8, 4]));
+            config.lconfig_raw();
+
+            assert_eq!(
+                config.request_lines().unwrap_err(),
+                ChipError::OperationFailed("Gpio LineRequest request-lines", IoError::new(EINVAL))
+            );
+        }
+    }
+
+    mod verify {
+        use super::*;
+
+        #[test]
+        fn custom_consumer() {
+            const GPIO: u32 = 2;
+            const CONSUMER: &str = "foobar";
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig_consumer(Some(&[GPIO]), Some(CONSUMER));
+            config.lconfig_raw();
+            config.request_lines().unwrap();
+
+            let info = config.chip().line_info(GPIO).unwrap();
+
+            assert_eq!(info.is_used(), true);
+            assert_eq!(info.get_consumer().unwrap(), CONSUMER);
+        }
+
+        #[test]
+        fn empty_consumer() {
+            const GPIO: u32 = 2;
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&[GPIO]));
+            config.lconfig_raw();
+            config.request_lines().unwrap();
+
+            let info = config.chip().line_info(GPIO).unwrap();
+
+            assert_eq!(info.is_used(), true);
+            assert_eq!(info.get_consumer().unwrap(), "?");
+        }
+
+        #[test]
+        fn read_values() {
+            let offsets = [7, 1, 0, 6, 2];
+            let pulls = [
+                GPIOSIM_PULL_UP,
+                GPIOSIM_PULL_UP,
+                GPIOSIM_PULL_DOWN,
+                GPIOSIM_PULL_UP,
+                GPIOSIM_PULL_DOWN,
+            ];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.set_pull(&offsets, &pulls);
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Input), None, None, None, None);
+            config.request_lines().unwrap();
+
+            let request = config.request();
+
+            // Buffer is smaller
+            let mut values: Vec<i32> = vec![0; 4];
+            assert_eq!(
+                request.get_values(&mut values).unwrap_err(),
+                ChipError::OperationFailed(
+                    "Gpio LineRequest array size mismatch",
+                    IoError::new(EINVAL),
+                )
+            );
+
+            // Buffer is larger
+            let mut values: Vec<i32> = vec![0; 6];
+            assert_eq!(
+                request.get_values(&mut values).unwrap_err(),
+                ChipError::OperationFailed(
+                    "Gpio LineRequest array size mismatch",
+                    IoError::new(EINVAL),
+                )
+            );
+
+            // Single values read properly
+            assert_eq!(request.get_value(7).unwrap(), 1);
+
+            // Values read properly
+            let mut values: Vec<i32> = vec![0; 5];
+            request.get_values(&mut values).unwrap();
+            for i in 0..values.len() {
+                assert_eq!(
+                    values[i],
+                    match pulls[i] {
+                        GPIOSIM_PULL_DOWN => 0,
+                        _ => 1,
+                    }
+                );
+            }
+
+            // Subset of values read properly
+            let mut values: Vec<i32> = vec![0; 3];
+            request.get_values_subset(&[2, 0, 6], &mut values).unwrap();
+            assert_eq!(values[0], 0);
+            assert_eq!(values[1], 0);
+            assert_eq!(values[2], 1);
+
+            // Value read properly after reconfigure
+            let mut lconfig = LineConfig::new().unwrap();
+            lconfig.set_active_low_default(true);
+            request.reconfigure_lines(&lconfig).unwrap();
+            assert_eq!(request.get_value(7).unwrap(), 0);
+        }
+
+        #[test]
+        fn set_output_values() {
+            let offsets = [0, 1, 3, 4];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Output), Some(1), Some((4, 0)), None, None);
+            config.request_lines().unwrap();
+
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+
+            // Overriden
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+            // Default
+            assert_eq!(config.sim().val(2).unwrap(), GPIOSIM_VALUE_INACTIVE);
+        }
+
+        #[test]
+        fn reconfigure_output_values() {
+            let offsets = [0, 1, 3, 4];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Output), Some(0), None, None, None);
+            config.request_lines().unwrap();
+            let request = config.request();
+
+            // Set single value
+            request.set_value(1, 1).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            request.set_value(1, 0).unwrap();
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+            // Set values of subset
+            request.set_values_subset(&[4, 3], &[1, 1]).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            request.set_values_subset(&[4, 3], &[0, 0]).unwrap();
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+            // Set all values
+            request.set_values(&[1, 0, 1, 0]).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            request.set_values(&[0, 0, 0, 0]).unwrap();
+            assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+            assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+        }
+
+        #[test]
+        fn set_bias() {
+            let offsets = [3];
+            let mut config = TestConfig::new(NGPIO).unwrap();
+            config.rconfig(Some(&offsets));
+            config.lconfig(Some(Direction::Input), None, None, None, Some(Bias::PullUp));
+            config.request_lines().unwrap();
+            config.request();
+
+            // Set single value
+            assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+        }
+    }
+}
diff --git a/bindings/rust/tests/request_config.rs b/bindings/rust/tests/request_config.rs
new file mode 100644
index 000000000000..e914ca8ec887
--- /dev/null
+++ b/bindings/rust/tests/request_config.rs
@@ -0,0 +1,42 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod request_config {
+    use vmm_sys_util::errno::Error as IoError;
+
+    use libgpiod::{Error as ChipError, RequestConfig};
+
+    mod verify {
+        use super::*;
+
+        #[test]
+        fn default() {
+            let rconfig = RequestConfig::new().unwrap();
+
+            assert_eq!(rconfig.get_offsets().len(), 0);
+            assert_eq!(rconfig.get_event_buffer_size(), 0);
+            assert_eq!(
+                rconfig.get_consumer().unwrap_err(),
+                ChipError::OperationFailed("Gpio RequestConfig get-consumer", IoError::new(0))
+            );
+        }
+
+        #[test]
+        fn initialized() {
+            let offsets = [0, 1, 2, 3];
+            const CONSUMER: &str = "foobar";
+            let rconfig = RequestConfig::new().unwrap();
+            rconfig.set_consumer(CONSUMER);
+            rconfig.set_offsets(&offsets);
+            rconfig.set_event_buffer_size(64);
+
+            assert_eq!(rconfig.get_offsets(), offsets);
+            assert_eq!(rconfig.get_event_buffer_size(), 64);
+            assert_eq!(rconfig.get_consumer().unwrap(), CONSUMER);
+        }
+    }
+}