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..a0fa13f 100644 --- a/src/exec/cli_exec.rs +++ b/src/exec/cli_exec.rs @@ -9,7 +9,7 @@ use crate::{ consts::DURATION, err, info, cli::tty::{DynTty, InnerTty, Tty, WrapperTty}, util::{anybase::AnyBase, util::rand_string} }; -use super::cli_api::CliTestApi; +use super::cli_api::{CliTestApi, SudoCliTestApi}; pub struct CliTester { inner: DynTty, @@ -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(); @@ -139,3 +120,84 @@ impl CliTestApi for CliTester { self.run_command(&cmd) } } + +pub struct SudoCliTester { + inner: CliTester, +} + +impl SudoCliTester { + pub fn build(inner: DynTty) -> SudoCliTester { + SudoCliTester { + inner: CliTester::build(inner), + } + } +} + +impl AnyBase for SudoCliTester { + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn into_any(self: Box) -> Box { + self + } +} + +impl Tty for SudoCliTester { + // Note: This will SKIP the logic in the tester + fn read(&mut self) -> Result, Box> { + self.inner.read() + } + // Note: This will SKIP the logic in the tester + fn read_line(&mut self) -> Result, Box> { + self.inner.read_line() + } + fn write(&mut self, data: &[u8]) -> Result<(), Box> { + self.inner.write(data) + } +} + +impl WrapperTty for SudoCliTester { + fn exit(self) -> DynTty { + self.inner.exit() + } +} + +impl InnerTty for SudoCliTester { + fn inner_ref(&self) -> &DynTty { + self.inner.inner_ref() + } + fn inner_mut(&mut self) -> &mut DynTty { + self.inner.inner_mut() + } +} + +impl CliTestApi for SudoCliTester { + 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> { + self.inner.script_run(script, timeout) + } + fn background_script_run(&mut self, script: &str) -> Result<(), Box> { + self.inner.background_script_run(script) + } + fn writeln(&mut self, script: &str) -> Result<(), Box> { + self.inner.writeln(script) + } +} + +impl SudoCliTestApi for SudoCliTester { + fn script_sudo( + &mut self, + script: &str, + timeout: u32, + ) -> Result> { + let mut cmd = String::from("sudo "); + cmd += script; + cmd += "\n"; + self.inner.script_run(&cmd, timeout) + } +} diff --git a/src/exec/cli_exec_sudo.rs b/src/exec/cli_exec_sudo.rs deleted file mode 100644 index 71ac45b..0000000 --- a/src/exec/cli_exec_sudo.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::any::Any; - -use crate::{ - cli::tty::{DynTty, InnerTty, Tty, WrapperTty}, - util::anybase::AnyBase, -}; - -use super::{ - cli_api::{CliTestApi, SudoCliTestApi}, - cli_exec::CliTester, -}; - -pub struct SudoCliTester { - inner: CliTester, -} - -impl SudoCliTester { - pub fn build(inner: DynTty) -> SudoCliTester { - SudoCliTester { - inner: CliTester::build(inner), - } - } -} - -impl AnyBase for SudoCliTester { - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - fn into_any(self: Box) -> Box { - self - } -} - -impl Tty for SudoCliTester { - // Note: This will SKIP the logic in the tester - fn read(&mut self) -> Result, Box> { - self.inner.read() - } - // Note: This will SKIP the logic in the tester - fn read_line(&mut self) -> Result, Box> { - self.inner.read_line() - } - fn write(&mut self, data: &[u8]) -> Result<(), Box> { - self.inner.write(data) - } -} - -impl WrapperTty for SudoCliTester { - fn exit(self) -> DynTty { - self.inner.exit() - } -} - -impl InnerTty for SudoCliTester { - fn inner_ref(&self) -> &DynTty { - self.inner.inner_ref() - } - fn inner_mut(&mut self) -> &mut DynTty { - self.inner.inner_mut() - } -} - -impl CliTestApi for SudoCliTester { - fn wait_serial(&mut self, expected: &str, timeout: u32) -> Result<(), Box> { - self.inner.wait_serial(expected, timeout) - } - fn script_run(&mut self, script: &str, timeout: u32) -> Result<(), Box> { - 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) - } - fn writeln(&mut self, script: &str) -> Result<(), Box> { - self.inner.writeln(script) - } -} - -impl SudoCliTestApi for SudoCliTester { - fn script_sudo( - &mut self, - script: &str, - timeout: u32, - ) -> Result<(), Box> { - 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..e123d88 --- /dev/null +++ b/src/exec/mod.rs @@ -0,0 +1,5 @@ +pub mod cli_api; +pub mod cli_exec; +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..bf25c90 100644 --- a/src/pythonapi/exec.rs +++ b/src/pythonapi/exec.rs @@ -4,7 +4,7 @@ use crate::{ exec::{ cli_api::{CliTestApi, SudoCliTestApi}, cli_exec::CliTester, - cli_exec_sudo::SudoCliTester, + cli_exec::SudoCliTester, }, util::anybase::heap_raw, }; @@ -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(()) } } diff --git a/src/pythonapi/shell_like.rs b/src/pythonapi/shell_like.rs index 1588a1a..76374a3 100644 --- a/src/pythonapi/shell_like.rs +++ b/src/pythonapi/shell_like.rs @@ -4,12 +4,16 @@ use pyo3::{exceptions::PyRuntimeError, prelude::*}; use serde::Deserialize; use crate::{ - exec::{cli_exec::CliTester, cli_exec_sudo::SudoCliTester}, - log, - pythonapi::{asciicast::handle_asciicast, tee::handle_tee}, cli::{ - asciicast::Asciicast, deansi::DeANSI, recorder::{Recorder, SimpleRecorder}, tee::Tee, tty::{DynTty, WrapperTty} + asciicast::Asciicast, + deansi::DeANSI, + recorder::{Recorder, SimpleRecorder}, + tee::Tee, + tty::{DynTty, WrapperTty}, }, + exec::cli_exec::{CliTester, SudoCliTester}, + log, + pythonapi::{asciicast::handle_asciicast, tee::handle_tee}, util::anybase::heap_raw, };