Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ mod tests {
org_url_slug: String::from("org_url_slug"),
test_identifiers: vec![],
remote_urls: vec![],
test_collection_short_id: None,
})
.await
.unwrap_err()
Expand Down Expand Up @@ -585,6 +586,7 @@ mod tests {
remote_urls: vec![],
org_url_slug: String::from("org_url_slug"),
test_identifiers: vec![],
test_collection_short_id: None,
})
.await
.unwrap_err()
Expand Down Expand Up @@ -628,6 +630,7 @@ mod tests {
remote_urls: vec![],
org_url_slug: String::from("org_url_slug"),
test_identifiers: vec![],
test_collection_short_id: None,
})
.await
.unwrap_err()
Expand Down Expand Up @@ -709,6 +712,7 @@ mod tests {
org_url_slug: String::from("org_url_slug"),
test_identifiers: vec![],
remote_urls: vec![],
test_collection_short_id: None,
})
.await;

Expand Down
57 changes: 57 additions & 0 deletions api/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,67 @@ pub struct CreateBundleUploadResponse {
pub test_collection_bundle_meta_created_at: Option<String>,
}

/// Which source the server resolved quarantine status from.
#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum QuarantineResolutionMode {
Repo,
TestCollection,
#[default]
Unspecified,
}

impl<'de> Deserialize<'de> for QuarantineResolutionMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(match serde_json::Value::deserialize(deserializer)?.as_str() {
Some("repo") => Self::Repo,
Some("test_collection") => Self::TestCollection,
_ => Self::Unspecified,
})
}
}

impl From<QuarantineResolutionMode> for proto::upload_metrics::trunk::QuarantineResolutionMode {
fn from(mode: QuarantineResolutionMode) -> Self {
match mode {
QuarantineResolutionMode::Repo => Self::Repo,
QuarantineResolutionMode::TestCollection => Self::TestCollection,
QuarantineResolutionMode::Unspecified => Self::Unspecified,
}
}
}

impl QuarantineResolutionMode {
pub fn resolution_log_line(
&self,
test_collection_id: Option<&str>,
repo: &RepoUrlParts,
) -> Option<String> {
match self {
Self::TestCollection => Some(format!(
"Resolved quarantine status for test collection {}",
test_collection_id.unwrap_or("unknown"),
)),
Self::Repo => Some(format!(
"Resolved quarantine status for repo {}",
repo.repo_full_name()
)),
Self::Unspecified => None,
}
}
}

#[derive(Debug, Serialize, Clone, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GetQuarantineConfigResponse {
pub is_disabled: bool,
#[serde(rename = "testIds")]
pub quarantined_tests: Vec<String>,
#[serde(default)]
pub quarantine_resolution_mode: QuarantineResolutionMode,
}

#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
Expand All @@ -39,6 +94,8 @@ pub struct GetQuarantineConfigRequest {
pub remote_urls: Vec<String>,
pub org_url_slug: String,
pub test_identifiers: Vec<Test>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub test_collection_short_id: Option<String>,
}

#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
Expand Down
3 changes: 3 additions & 0 deletions cli/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ pub async fn gather_exit_code_and_quarantined_tests_context(
api_client: &ApiClient,
file_set_builder: &FileSetBuilder,
default_exit_code: Option<i32>,
test_collection_short_id: Option<String>,
) -> anyhow::Result<QuarantineContext> {
// Run the quarantine step and update the exit code.
let failed_tests_extractor = FailedTestsExtractor::new(
Expand Down Expand Up @@ -801,6 +802,7 @@ pub async fn gather_exit_code_and_quarantined_tests_context(
repo: RepoUrlParts::default(),
org_url_slug: String::default(),
fetch_status: QuarantineFetchStatus::FetchSkipped,
quarantine_resolution_mode: api::message::QuarantineResolutionMode::Unspecified,
}
} else {
// default to success if no test run result (i.e. `upload`)
Expand All @@ -814,6 +816,7 @@ pub async fn gather_exit_code_and_quarantined_tests_context(
org_url_slug: meta.base_props.org.clone(),
test_identifiers: failed_tests_extractor.failed_tests().to_vec(),
remote_urls: vec![meta.base_props.repo.repo_url.clone()],
test_collection_short_id,
},
file_set_builder,
Some(failed_tests_extractor),
Expand Down
25 changes: 23 additions & 2 deletions cli/src/context_quarantine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
io::{BufReader, Read},
};

use api::{client::ApiClient, urls::url_for_test_case};
use api::{client::ApiClient, message::QuarantineResolutionMode, urls::url_for_test_case};
use bundle::{
FileSet, FileSetBuilder, FileSetTestRunnerReport, FileSetType, QuarantineBulkTestStatus, Test,
};
Expand Down Expand Up @@ -47,6 +47,7 @@ pub struct QuarantineContext {
pub repo: RepoUrlParts,
pub org_url_slug: String,
pub fetch_status: QuarantineFetchStatus,
pub quarantine_resolution_mode: QuarantineResolutionMode,
}
impl QuarantineContext {
pub fn skip_fetch(failures: Vec<Test>) -> Self {
Expand All @@ -57,6 +58,7 @@ impl QuarantineContext {
repo: RepoUrlParts::default(),
org_url_slug: String::default(),
fetch_status: QuarantineFetchStatus::FetchSkipped,
quarantine_resolution_mode: QuarantineResolutionMode::Unspecified,
}
}

Expand All @@ -68,6 +70,7 @@ impl QuarantineContext {
repo: RepoUrlParts::default(),
org_url_slug: String::default(),
fetch_status: QuarantineFetchStatus::FetchFailed(error),
quarantine_resolution_mode: QuarantineResolutionMode::Unspecified,
}
}
}
Expand Down Expand Up @@ -322,6 +325,7 @@ pub async fn gather_quarantine_context(
quarantine_status: QuarantineBulkTestStatus::default(),
failures: Vec::default(),
fetch_status: QuarantineFetchStatus::FetchSkipped,
quarantine_resolution_mode: QuarantineResolutionMode::Unspecified,
});
}

Expand All @@ -336,14 +340,27 @@ pub async fn gather_quarantine_context(
{
tracing::info!("Checking if failed tests can be quarantined");
match api_client.get_quarantining_config(request).await {
anyhow::Result::Ok(response) => (Some(response), QuarantineFetchStatus::FetchSucceeded),
anyhow::Result::Ok(response) => {
if let Some(line) = response.quarantine_resolution_mode.resolution_log_line(
request.test_collection_short_id.as_deref(),
&request.repo,
) {
tracing::info!("{line}");
}
(Some(response), QuarantineFetchStatus::FetchSucceeded)
}
anyhow::Result::Err(error) => (None, QuarantineFetchStatus::FetchFailed(error)),
}
} else {
tracing::debug!("Skipping quarantine check.");
(None, QuarantineFetchStatus::FetchSkipped)
};

let quarantine_resolution_mode = quarantine_config
.as_ref()
.map(|config| config.quarantine_resolution_mode)
.unwrap_or_default();

// if quarantining is not enabled, return exit code and empty quarantine status
if quarantine_config
.as_ref()
Expand All @@ -361,6 +378,7 @@ pub async fn gather_quarantine_context(
repo: request.repo.clone(),
org_url_slug: request.org_url_slug.clone(),
fetch_status: quarantine_fetch_status,
quarantine_resolution_mode,
});
} else {
// quarantining is enabled, continue with quarantine process and update exit code
Expand Down Expand Up @@ -447,6 +465,7 @@ pub async fn gather_quarantine_context(
repo: request.repo.clone(),
org_url_slug: request.org_url_slug.clone(),
fetch_status: quarantine_fetch_status,
quarantine_resolution_mode,
})
}

Expand Down Expand Up @@ -660,6 +679,7 @@ mod tests {
repo: RepoUrlParts::default(),
org_url_slug: String::new(),
fetch_status: QuarantineFetchStatus::FetchSucceeded,
quarantine_resolution_mode: QuarantineResolutionMode::Unspecified,
};
assert_eq!(
quarantine_query_result(false, &success_ctx),
Expand Down Expand Up @@ -689,6 +709,7 @@ mod tests {
failures: vec![],
repo: RepoUrlParts::default(),
org_url_slug: String::new(),
quarantine_resolution_mode: QuarantineResolutionMode::Unspecified,
};
assert_eq!(
quarantine_query_result(false, &repo_disabled_ctx),
Expand Down
11 changes: 11 additions & 0 deletions cli/src/upload_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ pub struct RunUploadOptions {
pub render_sender: Option<Sender<DisplayMessage>>,
pub quarantine_query_result_override:
Option<proto::upload_metrics::trunk::QuarantineQueryResult>,
pub quarantine_resolution_mode_override:
Option<proto::upload_metrics::trunk::QuarantineResolutionMode>,
}

impl Default for RunUploadOptions {
Expand All @@ -389,6 +391,7 @@ impl Default for RunUploadOptions {
test_run_result: None,
render_sender: None,
quarantine_query_result_override: None,
quarantine_resolution_mode_override: None,
}
}
}
Expand All @@ -402,6 +405,7 @@ pub async fn run_upload(
test_run_result,
render_sender,
quarantine_query_result_override,
quarantine_resolution_mode_override,
} = options;
// grab the exec start if provided (`test` subcommand) or use the current time
let cli_started_at = if let Some(test_run_result) = test_run_result.as_ref() {
Expand Down Expand Up @@ -483,6 +487,10 @@ pub async fn run_upload(
&api_client,
&file_set_builder,
default_exit_code,
upload_args
.test_collection_short_id
.clone()
.filter(|id| !id.is_empty()),
)
.await
{
Expand Down Expand Up @@ -562,6 +570,8 @@ pub async fn run_upload(
.await;
let quarantine_query_result = quarantine_query_result_override
.unwrap_or_else(|| quarantine_query_result(disable_quarantining, &quarantine_context));
let quarantine_resolution_mode = quarantine_resolution_mode_override
.unwrap_or_else(|| quarantine_context.quarantine_resolution_mode.into());
let upload_metrics = proto::upload_metrics::trunk::UploadMetrics {
client_version: Some(proto::upload_metrics::trunk::Semver {
major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or_default(),
Expand All @@ -580,6 +590,7 @@ pub async fn run_upload(
failed: false,
failure_reason: "".into(),
quarantine_query_result: quarantine_query_result.into(),
quarantine_resolution_mode: quarantine_resolution_mode.into(),
};
let mut request = api::message::TelemetryUploadMetricsRequest { upload_metrics };
if !upload_args.dry_run {
Expand Down
3 changes: 3 additions & 0 deletions cli/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ async fn quarantining_resets_fail_code() {
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down Expand Up @@ -347,6 +348,7 @@ async fn quarantining_not_active_when_disable_quarantining_set() {
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down Expand Up @@ -408,6 +410,7 @@ async fn quarantining_not_active_when_disable_true_but_use_true() {
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down
11 changes: 11 additions & 0 deletions cli/tests/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ async fn upload_bundle() {
assert_eq!(req.repo.owner, "trunk-io");
assert_eq!(req.repo.name, "analytics-cli");
assert_eq!(req.org_url_slug, "test-org");
assert_eq!(
req.test_collection_short_id,
Some(String::from("tc_123")),
"--test-collection-id must reach the getQuarantineConfig request"
);
assert!(
!req.test_identifiers.is_empty(),
"test_identifiers should not be empty"
Expand Down Expand Up @@ -1294,6 +1299,7 @@ async fn quarantines_tests_regardless_of_upload() {
Json(GetQuarantineConfigResponse {
is_disabled,
quarantined_tests,
..Default::default()
})
}
},
Expand Down Expand Up @@ -2041,6 +2047,7 @@ async fn do_not_quarantines_tests_when_quarantine_disabled_set() {
Json(GetQuarantineConfigResponse {
is_disabled,
quarantined_tests,
..Default::default()
})
}
},
Expand Down Expand Up @@ -2149,6 +2156,7 @@ async fn still_quarantines_if_upload_to_s3_fails() {
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down Expand Up @@ -2189,6 +2197,7 @@ async fn still_quarantines_if_upload_endpoint_fails() {
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down Expand Up @@ -2229,6 +2238,7 @@ async fn presents_error_message_if_you_dont_have_permission_for_s3_upload() {
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down Expand Up @@ -2270,6 +2280,7 @@ async fn presents_error_message_if_you_dont_have_permission_for_upload_endpoint(
Json(GetQuarantineConfigResponse {
is_disabled: false,
quarantined_tests: test_ids,
..Default::default()
})
}
},
Expand Down
8 changes: 8 additions & 0 deletions proto/proto/upload_metrics.proto
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ enum QuarantineQueryResult {
QUARANTINE_QUERY_RESULT_CACHED = 5;
}

// Which source the server used to resolve quarantine status
enum QuarantineResolutionMode {
QUARANTINE_RESOLUTION_MODE_UNSPECIFIED = 0;
QUARANTINE_RESOLUTION_MODE_REPO = 1;
QUARANTINE_RESOLUTION_MODE_TEST_COLLECTION = 2;
}

message UploadMetrics {
Semver client_version = 1;
Repo repo = 2;
Expand All @@ -29,6 +36,7 @@ message UploadMetrics {
bool failed = 6;
string failure_reason = 7;
QuarantineQueryResult quarantine_query_result = 8;
QuarantineResolutionMode quarantine_resolution_mode = 9;
}

// Used by the analytics-uploader, kept here to avoid collisions in the future.
Expand Down
Loading
Loading