Skip to content

Commit

Permalink
feat(android): --pull-files arg was added for android run (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
matzuk authored Jun 11, 2024
1 parent 8fad6a1 commit ff1b9a5
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 1 deletion.
10 changes: 10 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use tokio_util::io::ReaderStream;
use crate::{
errors::{ApiError, EnvArgError, InputError},
filtering::model::SparseMarathonfile,
pull::PullFileConfig,
};

#[async_trait]
Expand Down Expand Up @@ -47,6 +48,7 @@ pub trait RapiClient {
flavor: Option<String>,
env_args: Option<Vec<String>>,
test_env_args: Option<Vec<String>>,
pull_file_config: Option<PullFileConfig>,
) -> Result<String>;
async fn get_run(&self, id: &str) -> Result<TestRun>;

Expand Down Expand Up @@ -135,6 +137,7 @@ impl RapiClient for RapiReqwestClient {
flavor: Option<String>,
env_args: Option<Vec<String>>,
test_env_args: Option<Vec<String>>,
pull_file_config: Option<PullFileConfig>,
) -> Result<String> {
let url = format!("{}/run", self.base_url);
let params = [("api_key", self.api_key.clone())];
Expand Down Expand Up @@ -330,6 +333,13 @@ impl RapiClient for RapiReqwestClient {
);
}

if let Some(pull_file_config) = pull_file_config {
form = form.text(
"pull_file_config",
serde_json::to_string(&pull_file_config)?,
);
}

let response = self.client.post(url).multipart(form).send().await?;
let response = api_error_adapter(response)
.await?
Expand Down
9 changes: 9 additions & 0 deletions src/cli/android/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::pull::parse_pull_args;
use anyhow::Result;
use std::fmt::Display;

Expand All @@ -6,6 +7,7 @@ use crate::{
errors::ConfigurationError,
filtering,
interactor::TriggerTestRunInteractor,
pull::PullFileConfig,
};

#[derive(Debug, clap::ValueEnum, Clone)]
Expand Down Expand Up @@ -83,6 +85,7 @@ pub(crate) async fn run(
instrumentation_arg: Option<Vec<String>>,
retry_args: RetryArgs,
analytics_args: AnalyticsArgs,
pull_files: Option<Vec<String>>,
) -> Result<bool> {
match (device.as_deref(), &flavor, &system_image, &os_version) {
(Some("watch"), _, Some(SystemImage::Default) | None, Some(_) | None)
Expand Down Expand Up @@ -130,6 +133,11 @@ pub(crate) async fn run(
let retry_args = cli::validate::retry_args(retry_args);
cli::validate::result_file_args(&common.result_file_args)?;

let pull_file_config: Option<PullFileConfig> = match pull_files {
Some(args) => Some(parse_pull_args(args)?),
None => None,
};

let present_wait: bool = match common.wait {
None => true,
Some(true) => true,
Expand Down Expand Up @@ -164,6 +172,7 @@ pub(crate) async fn run(
common.result_file_args.result_file,
instrumentation_arg,
None,
pull_file_config,
)
.await
}
1 change: 1 addition & 0 deletions src/cli/ios/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ If you provide any single or two of these parameters, the others will be inferre
common.result_file_args.result_file,
xctestrun_env,
xctestrun_test_env,
None,
)
.await
}
Expand Down
8 changes: 8 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ impl Cli {
instrumentation_arg,
retry_args,
analytics_args,
pull_files,
} => {
android::run(
application,
Expand All @@ -66,6 +67,7 @@ impl Cli {
instrumentation_arg,
retry_args,
analytics_args,
pull_files,
)
.await
}
Expand Down Expand Up @@ -410,6 +412,12 @@ enum RunCommands {

#[arg(long, help = "Instrumentation arguments, example: FOO=BAR")]
instrumentation_arg: Option<Vec<String>>,

#[arg(
long,
help = "Pull files from devices after the test run. The format is 'ROOT:PATH' where ROOT is one of [EXTERNAL_STORAGE, APP_DATA] and PATH is a relative path to the target file or directory. Example: 'EXTERNAL_STORAGE:Documents/some-results', 'APP_DATA:files/my_folder/some_file.txt'. Note: Files with the same name and path from different devices may overwrite each other."
)]
pull_files: Option<Vec<String>>,
},
#[allow(non_camel_case_types)]
#[command(name = "ios")]
Expand Down
11 changes: 11 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ pub enum EnvArgError {
MissingValue { env_arg: String },
}

#[derive(Error, Debug, PartialEq)]
pub enum PullArgError {
#[error(
"Invalid format for --pull-files argument. Expected format: ROOT:PATH. Your format: {arg}"
)]
InvalidFormat { arg: String },

#[error("Invalid root type for --pull-files argument. Expected types: [EXTERNAL_STORAGE, APP_DATA]. Your type: {used_type}")]
InvalidRootType { used_type: String },
}

#[derive(Error, Debug)]
pub enum ArtifactError {
#[error("Failed to retrieve artifact list.\nerror = {error}")]
Expand Down
4 changes: 3 additions & 1 deletion src/interactor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cli::model::Platform;
use crate::{cli::model::Platform, pull::PullFileConfig};
use anyhow::Result;
use globset::Glob;
use indicatif::{HumanDuration, ProgressBar, ProgressStyle};
Expand Down Expand Up @@ -125,6 +125,7 @@ impl TriggerTestRunInteractor {
result_file: Option<PathBuf>,
env_args: Option<Vec<String>>,
test_env_args: Option<Vec<String>>,
pull_file_config: Option<PullFileConfig>,
) -> Result<bool> {
let client = RapiReqwestClient::new(base_url, api_key);
let steps = match (wait, output) {
Expand Down Expand Up @@ -159,6 +160,7 @@ impl TriggerTestRunInteractor {
flavor,
env_args,
test_env_args,
pull_file_config,
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ mod filtering;
mod formatter;
mod interactor;
mod progress;
mod pull;
133 changes: 133 additions & 0 deletions src/pull.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use crate::errors::{self, PullArgError};
use serde::{Deserialize, Serialize};

const AGGREGATION_MODE_TEST_RUN: &str = "TEST_RUN";

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct PullFileConfig {
#[serde(rename = "pull")]
pub pull_items: Vec<PullFileItem>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct PullFileItem {
#[serde(rename = "relativePath")]
pub relative_path: String,
#[serde(rename = "aggregationMode")]
pub aggregation_mode: String,
#[serde(rename = "pathRoot")]
pub path_root: String,
}

pub fn parse_pull_args(pull_args: Vec<String>) -> Result<PullFileConfig, errors::PullArgError> {
let mut pulls = Vec::new();
for arg in pull_args {
let parts: Vec<&str> = arg.split(':').collect();
if parts.len() != 2 {
return Err(PullArgError::InvalidFormat {
arg: arg.to_string(),
});
}
let root = match parts[0] {
"EXTERNAL_STORAGE" => "EXTERNAL_STORAGE",
"APP_DATA" => "APP_DATA",
_ => {
return Err(PullArgError::InvalidRootType {
used_type: parts[0].to_string(),
})
}
};
let relative_path = parts[1].to_string();
pulls.push(PullFileItem {
relative_path,
aggregation_mode: AGGREGATION_MODE_TEST_RUN.to_string(),
path_root: root.to_string(),
});
}
let pull_file_config = PullFileConfig { pull_items: pulls };
Ok(pull_file_config)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_valid_pull_args() {
let pull_args = vec![
"EXTERNAL_STORAGE:my-device-folder1".to_string(),
"APP_DATA:my-device-folder2/some_file.txt".to_string(),
];
let result = parse_pull_args(pull_args);

assert!(result.is_ok());
let pull_file_config = result.unwrap();
let pulls = pull_file_config.pull_items;
assert_eq!(pulls.len(), 2);

assert_eq!(
pulls[0],
PullFileItem {
relative_path: "my-device-folder1".to_string(),
aggregation_mode: AGGREGATION_MODE_TEST_RUN.to_string(),
path_root: "EXTERNAL_STORAGE".to_string(),
}
);

assert_eq!(
pulls[1],
PullFileItem {
relative_path: "my-device-folder2/some_file.txt".to_string(),
aggregation_mode: AGGREGATION_MODE_TEST_RUN.to_string(),
path_root: "APP_DATA".to_string(),
}
);
}

#[test]
fn test_invalid_format_pull_arg() {
let pull_args = vec![
"EXTERNAL_STORAGE:my-device-folder1".to_string(),
"INVALID_FORMAT".to_string(),
];
let result = parse_pull_args(pull_args);

assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
PullArgError::InvalidFormat {
arg: "INVALID_FORMAT".to_string()
}
);
}

#[test]
fn test_invalid_root_type_pull_arg() {
let pull_args = vec![
"EXTERNAL_STORAGE:my-device-folder1".to_string(),
"UNKNOWN:my-device-folder2".to_string(),
];
let result = parse_pull_args(pull_args);

assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
PullArgError::InvalidRootType {
used_type: "UNKNOWN".to_string()
}
);
}

#[test]
fn test_empty_pull_args() {
let pull_args: Vec<String> = Vec::new();
let result = parse_pull_args(pull_args);

assert!(result.is_ok());
let pull_file_config = result.unwrap();
let pulls = pull_file_config.pull_items;
assert_eq!(pulls.len(), 0);
}
}

0 comments on commit ff1b9a5

Please sign in to comment.