diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9ee4df2..fe029db 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -37,11 +37,11 @@ jobs: - runner: ubuntu-latest target: ppc64le steps: + - uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y pkg-config libssl-dev openssl - - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x @@ -72,11 +72,11 @@ jobs: - runner: ubuntu-latest target: armv7 steps: + - uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y pkg-config libssl-dev openssl - - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x @@ -103,10 +103,10 @@ jobs: - runner: windows-latest target: x86 steps: + - uses: actions/checkout@v4 - name: Install dependencies run: | choco install -y openssl - - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x @@ -133,10 +133,10 @@ jobs: - runner: macos-14 target: aarch64 steps: + - uses: actions/checkout@v4 - name: Install dependencies run: | brew install openssl@1.1 - - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x diff --git a/.gitignore b/.gitignore index 1c3aa0f..df0a8f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target *.log *.cast +*.deprecated # Byte-compiled / optimized / DLL files diff --git a/src/exec/cli_api.rs b/src/exec/cli_api.rs index 1581475..c4c7bf6 100644 --- a/src/exec/cli_api.rs +++ b/src/exec/cli_api.rs @@ -3,16 +3,18 @@ use std::error::Error; use crate::cli::tty::InnerTty; pub trait CliTestApi: InnerTty { - fn script_run(&mut self, script: &str, timeout: u32) -> Result<(), Box>; - fn assert_script_run(&mut self, script: &str, timeout: u32) -> Result<(), Box>; + /// + /// + /// You may found this func includes assert_script_run and script_output + fn script_run(&mut self, script: &str, timeout: u32) -> Result>; fn background_script_run(&mut self, script: &str) -> Result<(), Box>; fn writeln(&mut self, script: &str) -> Result<(), Box>; - fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result<(), Box>; - // fn script_output(&mut self, script: &str) -> Result, Box>; - // fn validate_script_output(&mut self, script: &str, expected_output: &str) -> Result<(), Box>; + fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result>; } pub trait SudoCliTestApi: CliTestApi { - fn script_sudo(&mut self, script: &str, timeout: u32) -> Result<(), Box>; - fn assert_script_sudo(&mut self, script: &str, timeout: u32) -> Result<(), Box>; + /// + /// + /// You may found this func includes assert_script_sudo and script_output + fn script_sudo(&mut self, script: &str, timeout: u32) -> Result>; } diff --git a/src/exec/cli_exec.rs b/src/exec/cli_exec.rs index 2ae737e..da3875f 100644 --- a/src/exec/cli_exec.rs +++ b/src/exec/cli_exec.rs @@ -71,7 +71,7 @@ impl InnerTty for CliTester { } impl CliTestApi for CliTester { - fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result<(), Box> { + fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result> { let begin = Instant::now(); let mut buf = Vec::new(); info!("Waiting for string {{{}}}", expected); @@ -94,9 +94,10 @@ impl CliTestApi for CliTester { } } - Ok(()) + let res = String::from_utf8(buf)?; + Ok(res) } - fn script_run(&mut self, script: &str, timeout: u32) -> Result<(), Box> { + fn script_run(&mut self, script: &str, timeout: u32) -> Result> { let mut cmd = script.to_owned(); let echo_content_rand = String::from_utf8(rand_string(8)).unwrap(); @@ -106,27 +107,7 @@ impl CliTestApi for CliTester { self.run_command(&cmd)?; - let res = self.wait_serial(&echo_content_rand, timeout); - if let Err(e) = res { - if e.to_string() == "Timeout" { - return Ok(()); - } - return Err(e); - } - Ok(()) - } - fn assert_script_run(&mut self, script: &str, timeout: u32) -> Result<(), Box> { - let mut cmd = script.to_owned(); - let echo_content_rand = String::from_utf8(rand_string(8)).unwrap(); - - cmd += "&& echo "; - cmd += &echo_content_rand; - cmd += " \n"; - - self.run_command(&cmd)?; - - self.wait_serial(&echo_content_rand, timeout)?; - Ok(()) + self.wait_serial(&echo_content_rand, timeout) } fn background_script_run(&mut self, script: &str) -> Result<(), Box> { let mut cmd = script.to_owned(); diff --git a/src/exec/cli_exec_sudo.rs b/src/exec/cli_exec_sudo.rs index 71ac45b..c665d45 100644 --- a/src/exec/cli_exec_sudo.rs +++ b/src/exec/cli_exec_sudo.rs @@ -64,19 +64,12 @@ impl InnerTty for SudoCliTester { } impl CliTestApi for SudoCliTester { - fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result<(), Box> { + fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result> { self.inner.wait_serial(expected, timeout) } - fn script_run(&mut self, script: &str, timeout: u32) -> Result<(), Box> { + fn script_run(&mut self, script: &str, timeout: u32) -> Result> { self.inner.script_run(script, timeout) } - fn assert_script_run( - &mut self, - script: &str, - timeout: u32, - ) -> Result<(), Box> { - self.inner.assert_script_run(script, timeout) - } fn background_script_run(&mut self, script: &str) -> Result<(), Box> { self.inner.background_script_run(script) } @@ -90,19 +83,10 @@ impl SudoCliTestApi for SudoCliTester { &mut self, script: &str, timeout: u32, - ) -> Result<(), Box> { + ) -> Result> { let mut cmd = String::from("sudo "); cmd += script; cmd += "\n"; self.inner.script_run(&cmd, timeout) } - fn assert_script_sudo( - &mut self, - script: &str, - timeout: u32, - ) -> Result<(), Box> { - let mut cmd = String::from("sudo "); - cmd += script; - self.inner.assert_script_run(&cmd, timeout) - } } diff --git a/src/exec/gui_api.rs b/src/exec/gui_api.rs new file mode 100644 index 0000000..d94edbf --- /dev/null +++ b/src/exec/gui_api.rs @@ -0,0 +1,28 @@ +//! Api for GUI part testing +//! +//! For GUI part, basicially we need to check the following: +//! - Is the current screen is the expected screen? +//! - Does the screen have the expected elements? +//! - Can we interact with the screen? + +use std::error::Error; + +use crate::gui::screen::Screen; + +use super::needle::Needle; + +/// The API can used for testing, with bypass [`Screen`] operations. +pub trait GuiTestApi: Screen { + /// Check if the current screen is the expected screen + fn assert_screen(&mut self, needle: Needle) -> Result<(), Box>; + /// Check and click the target position + /// + /// Suggest using assert_screen and click seperately, as futher consider adding relative position etc. + fn assert_screen_click(&mut self, needle: Needle) -> Result<(), Box>; + + /// Wait until current screen changed + fn wait_screen_change(&mut self, timeout: u32) -> Result<(), Box>; + + /// Wait and assert the screen won't change in timeout + fn wait_still_screen(&mut self, timeout: u32) -> Result<(), Box>; +} diff --git a/src/exec/mod.rs b/src/exec/mod.rs new file mode 100644 index 0000000..a5529ab --- /dev/null +++ b/src/exec/mod.rs @@ -0,0 +1,6 @@ +pub mod cli_api; +pub mod cli_exec; +pub mod cli_exec_sudo; +pub mod gui_api; + +pub mod needle; \ No newline at end of file diff --git a/src/exec/needle.rs b/src/exec/needle.rs new file mode 100644 index 0000000..ac1b388 --- /dev/null +++ b/src/exec/needle.rs @@ -0,0 +1,69 @@ +//! Object to match the GUI +//! +//! We can't do the same thing as the CLI part: give a string and wait for +//! it. We need to match the GUI with a needle. + +use image::RgbaImage; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum Needle { + /// Basic needle, just compare the screen with given image to see if the similarity is enough + Basic(Vec), + // In future, consider adding more needle types, + // like using neural network to match the screen + // to get a better flexibility +} + +/// Needle type +/// OpenQA needle compatible, with same definition +/// +/// See +#[derive(Serialize, Deserialize)] +pub enum NeedleType { + #[serde(rename = "match")] + Match, + #[serde(rename = "ocr")] + Ocr, // Currently not supported + #[serde(rename = "exclude")] + Exclude, +} + +/// Click point +/// OpenQA needle compatible, with same definition +/// +/// See +#[derive(Serialize, Deserialize)] +pub struct ClickPoint { + pub xpos: u32, + pub ypos: u32, +} + +/// Area to match +/// OpenQA needle compatible, with same definition +/// +/// See +#[derive(Serialize, Deserialize)] +pub struct Area { + /// X coordinate of the top left corner + pub x: u32, + /// Y coordinate of the top left corner + pub y: u32, + /// Width of the area + pub width: u32, + /// Height of the area + pub height: u32, + /// Type of the needle + pub needle: NeedleType, + /// The similarity threshold + #[serde(rename = "match")] + pub match_threhold: f32, + /// The click point + pub click_point: Option, + /// The image to match + /// + /// This field should be auto added by needle readers + #[serde(skip_serializing, skip_deserializing)] + pub target: Option, +} diff --git a/src/exec/runner.rs b/src/exec/runner.rs deleted file mode 100644 index 23f267d..0000000 --- a/src/exec/runner.rs +++ /dev/null @@ -1,82 +0,0 @@ -use super::cli_api::SudoCliTestApi; - -pub struct Cmd { - cmd: String, -} - -pub struct TimeoutCmd { - cmd: String, - timeout: u32, -} - -pub struct WaitCmd { - cmd: String, - wait: String, - timeout: u32, -} - -pub enum CmdType { - Direct(Cmd), // writeln(cmd) - Run(TimeoutCmd), // script_run(cmd, timeout) - AssertRun(TimeoutCmd), // assert_script_run(cmd, timeout) - SudoRun(TimeoutCmd), // script_sudo(cmd, timeout) - SudoAssertRun(TimeoutCmd), // assert_script_sudo(cmd, timeout) - Wait(TimeoutCmd), // wait_serial(cmd, timeout) - WaitRun(WaitCmd), // writeln(cmd) wait_serial(wait, timeout) -} - -pub struct CmdRunner { - cmds: Vec, - inner: Box, -} - -impl CmdRunner { - pub fn build(inner: Box, cmds: Vec) -> Self { - Self { cmds, inner } - } -} - -impl CmdRunner { - pub fn inner_ref(&self) -> &Box { - &self.inner - } - pub fn inner_mut(&mut self) -> &mut Box { - &mut self.inner - } -} - -impl CmdRunner { - pub fn renew(&mut self, cmds: Vec) { - self.cmds = cmds; - } - pub fn run(&mut self) { - for cmd in self.cmds.iter() { - match cmd { - CmdType::Direct(cmd) => { - self.inner.writeln(&cmd.cmd).unwrap(); - } - CmdType::Run(cmd) => { - self.inner.script_run(&cmd.cmd, cmd.timeout).unwrap(); - } - CmdType::AssertRun(cmd) => { - self.inner.assert_script_run(&cmd.cmd, cmd.timeout).unwrap(); - } - CmdType::SudoRun(cmd) => { - self.inner.script_sudo(&cmd.cmd, cmd.timeout).unwrap(); - } - CmdType::SudoAssertRun(cmd) => { - self.inner - .assert_script_sudo(&cmd.cmd, cmd.timeout) - .unwrap(); - } - CmdType::Wait(cmd) => { - self.inner.wait_serial(&cmd.cmd, cmd.timeout).unwrap(); - } - CmdType::WaitRun(cmd) => { - self.inner.writeln(&cmd.cmd).unwrap(); - self.inner.wait_serial(&cmd.wait, cmd.timeout).unwrap(); - } - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 3da35b5..f7f463e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,7 @@ pub mod consts; pub mod cli; pub mod gui; -pub mod exec { - pub mod cli_api; - pub mod cli_exec; - pub mod cli_exec_sudo; - - pub mod runner; -} +pub mod exec; pub mod devhost { pub mod devhost; pub mod sdwirec; diff --git a/src/pythonapi/exec.rs b/src/pythonapi/exec.rs index a0d92ca..3bc67e7 100644 --- a/src/pythonapi/exec.rs +++ b/src/pythonapi/exec.rs @@ -58,7 +58,7 @@ impl Exec { mut self_: PyRefMut<'_, Self>, script: &str, timeout: Option, - ) -> PyResult<()> { + ) -> PyResult { let self_ = self_.as_mut(); let inner = self_.inner.get_mut()?; let inner = inner.as_any_mut(); @@ -67,50 +67,21 @@ impl Exec { if inner.downcast_ref::().is_some() { let inner = inner.downcast_mut::().unwrap(); - inner + let res = inner .script_run(script, timeout) .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(res) } else if inner.downcast_ref::().is_some() { let inner = inner.downcast_mut::().unwrap(); - inner + let res = inner .script_run(script, timeout) .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(res) } else { - return Err(PyRuntimeError::new_err( + Err(PyRuntimeError::new_err( "Can't find the right object to run the script", - )); + )) } - Ok(()) - } - - #[pyo3(signature = (script, timeout=None))] - fn assert_script_run( - mut self_: PyRefMut<'_, Self>, - script: &str, - timeout: Option, - ) -> PyResult<()> { - let self_ = self_.as_mut(); - let inner = self_.inner.get_mut()?; - let inner = inner.as_any_mut(); - - let timeout = timeout.unwrap_or(30); - - if inner.downcast_ref::().is_some() { - let inner = inner.downcast_mut::().unwrap(); - inner - .assert_script_run(script, timeout) - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; - } else if inner.downcast_ref::().is_some() { - let inner = inner.downcast_mut::().unwrap(); - inner - .assert_script_run(script, timeout) - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; - } else { - return Err(PyRuntimeError::new_err( - "Can't find the right object to run the script", - )); - } - Ok(()) } fn background_script_run(mut self_: PyRefMut<'_, Self>, script: &str) -> PyResult<()> { @@ -164,7 +135,7 @@ impl Exec { mut self_: PyRefMut<'_, Self>, expected: &str, timeout: Option, - ) -> PyResult<()> { + ) -> PyResult { let self_ = self_.as_mut(); let inner = self_.inner.get_mut()?; let inner = inner.as_any_mut(); @@ -173,20 +144,21 @@ impl Exec { if inner.downcast_ref::().is_some() { let inner = inner.downcast_mut::().unwrap(); - inner + let res = inner .wait_serial(expected, timeout) .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(res) } else if inner.downcast_ref::().is_some() { let inner = inner.downcast_mut::().unwrap(); - inner + let res = inner .wait_serial(expected, timeout) .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(res) } else { - return Err(PyRuntimeError::new_err( + Err(PyRuntimeError::new_err( "Can't find the right object to run the script", - )); + )) } - Ok(()) } #[pyo3(signature = (script, timeout=None))] @@ -194,7 +166,7 @@ impl Exec { mut self_: PyRefMut<'_, Self>, script: &str, timeout: Option, - ) -> PyResult<()> { + ) -> PyResult { let self_ = self_.as_mut(); let inner = self_.inner.get_mut()?; let inner = inner.as_any_mut(); @@ -203,39 +175,14 @@ impl Exec { if inner.downcast_ref::().is_some() { let inner = inner.downcast_mut::().unwrap(); - inner + let res = inner .script_sudo(script, timeout) .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(res) } else { - return Err(PyRuntimeError::new_err( + Err(PyRuntimeError::new_err( "Can't find the right object to run the script", - )); + )) } - Ok(()) - } - - #[pyo3(signature = (script, timeout=None))] - fn assert_script_sudo( - mut self_: PyRefMut<'_, Self>, - script: &str, - timeout: Option, - ) -> PyResult<()> { - let self_ = self_.as_mut(); - let inner = self_.inner.get_mut()?; - let inner = inner.as_any_mut(); - - let timeout = timeout.unwrap_or(30); - - if inner.downcast_ref::().is_some() { - let inner = inner.downcast_mut::().unwrap(); - inner - .assert_script_sudo(script, timeout) - .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; - } else { - return Err(PyRuntimeError::new_err( - "Can't find the right object to run the script", - )); - } - Ok(()) } }