Skip to content

Commit dad5cce

Browse files
committed
init
1 parent e0f8450 commit dad5cce

File tree

13 files changed

+362
-65
lines changed

13 files changed

+362
-65
lines changed

Diff for: Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: api/src/client.rs

+35
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,41 @@ impl ApiClient {
184184
.await
185185
}
186186

187+
pub async fn list_quarantined_tests(
188+
&self,
189+
request: &message::ListQuarantinedTestsRequest,
190+
) -> anyhow::Result<message::ListQuarantinedTestsResponse> {
191+
CallApi {
192+
action: || async {
193+
let response = self
194+
.trunk_api_client
195+
.post(format!("{}{}/flaky-tests/list-quarantined-tests", self.api_host, self.version_path_prefix))
196+
.json(&request)
197+
.send()
198+
.await?;
199+
200+
let response = status_code_help(
201+
response,
202+
CheckUnauthorized::Check,
203+
CheckNotFound::DoNotCheck,
204+
|_| String::from("Failed to list quarantined tests."),
205+
&self.api_host,
206+
&self.org_url_slug,
207+
)?;
208+
209+
self.deserialize_response::<message::ListQuarantinedTestsResponse>(response).await
210+
},
211+
log_progress_message: |time_elapsed, _| {
212+
format!("Listing quarantined tests from Trunk services is taking longer than expected. It has taken {} seconds so far.", time_elapsed.as_secs())
213+
},
214+
report_slow_progress_message: |time_elapsed| {
215+
format!("Listing quarantined tests from Trunk services is taking longer than {} seconds", time_elapsed.as_secs())
216+
},
217+
}
218+
.call_api()
219+
.await
220+
}
221+
187222
pub async fn put_bundle_to_s3<U: AsRef<str>, B: AsRef<Path>>(
188223
&self,
189224
url: U,

Diff for: api/src/message.rs

+53
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,56 @@ pub struct CreateBundleUploadIntentResponse {
6363
pub struct TelemetryUploadMetricsRequest {
6464
pub upload_metrics: proto::upload_metrics::trunk::UploadMetrics,
6565
}
66+
67+
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
68+
pub struct PageQuery {
69+
#[serde(rename = "pageSize")]
70+
pub page_size: i32,
71+
#[serde(rename = "pageToken")]
72+
pub page_token: String,
73+
}
74+
75+
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
76+
pub struct Page {
77+
pub total_rows: i32,
78+
pub total_pages: i32,
79+
pub next_page_token: String,
80+
pub prev_page_token: String,
81+
pub last_page_token: String,
82+
pub page_index: i32,
83+
}
84+
85+
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
86+
pub struct ListQuarantinedTestsRequest {
87+
pub repo: RepoUrlParts,
88+
#[serde(rename = "orgUrlSlug")]
89+
pub org_url_slug: String,
90+
#[serde(rename = "pageQuery")]
91+
pub page_query: PageQuery,
92+
}
93+
94+
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
95+
pub enum QuarantineSetting {
96+
#[serde(rename = "ALWAYS_QUARANTINE")]
97+
AlwaysQuarantine,
98+
#[serde(rename = "AUTO_QUARANTINE")]
99+
AutoQuarantine,
100+
}
101+
102+
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
103+
pub struct QuarantinedTest {
104+
pub name: String,
105+
pub parent: Option<String>,
106+
pub file: Option<String>,
107+
#[serde(rename = "className")]
108+
pub class_name: Option<String>,
109+
pub status: String,
110+
pub quarantine_setting: QuarantineSetting,
111+
pub test_case_id: String,
112+
}
113+
114+
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
115+
pub struct ListQuarantinedTestsResponse {
116+
pub quarantined_tests: Vec<QuarantinedTest>,
117+
pub page: Page,
118+
}

Diff for: bundle/src/types.rs

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct Test {
2222
pub id: String,
2323
/// Added in v0.6.9
2424
pub timestamp_millis: Option<i64>,
25+
pub is_quarantined: bool,
2526
}
2627

2728
impl Test {
@@ -41,6 +42,7 @@ impl Test {
4142
file,
4243
id: String::with_capacity(0),
4344
timestamp_millis,
45+
is_quarantined: false,
4446
};
4547

4648
test.set_id(org_slug, repo);

Diff for: cli/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ bundle = { path = "../bundle" }
1919
constants = { path = "../constants" }
2020
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
2121
clap = { version = "4.4.18", features = ["derive", "env"] }
22-
context = { path = "../context" }
22+
context = { path = "../context", features = ["bindings"] }
2323
third-party = { path = "../third-party" }
2424
env_logger = { version = "0.11.0", default-features = false }
2525
log = "0.4.14"
@@ -54,6 +54,7 @@ proto = { path = "../proto" }
5454
regex = "1.11.1"
5555
lazy_static = "1.5.0"
5656
url = "2.5.4"
57+
prost = "0.12.6"
5758

5859
[dev-dependencies]
5960
test_utils = { version = "0.1.0", path = "../test_utils" }

Diff for: cli/src/context_quarantine.rs

+71-34
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
use std::collections::HashMap;
1+
use std::{
2+
collections::HashMap,
3+
io::{BufReader, Read},
4+
};
25

36
use api::{client::ApiClient, urls::url_for_test_case};
4-
use bundle::{FileSet, FileSetBuilder, QuarantineBulkTestStatus, Test};
7+
use bundle::{FileSet, FileSetBuilder, FileSetType, QuarantineBulkTestStatus, Test};
58
use constants::{EXIT_FAILURE, EXIT_SUCCESS};
69
use context::{
7-
junit::{junit_path::JunitReportStatus, parser::JunitParser},
10+
junit::{
11+
bindings::{
12+
BindingsReport, BindingsTestCase, BindingsTestCaseStatusStatus, BindingsTestSuite,
13+
},
14+
junit_path::JunitReportStatus,
15+
parser::JunitParser,
16+
},
817
repo::RepoUrlParts,
918
};
10-
use quick_junit::TestCaseStatus;
19+
use prost::Message;
1120

1221
use crate::error_report::{log_error, Context};
1322

@@ -21,27 +30,28 @@ fn convert_case_to_test<T: AsRef<str>>(
2130
repo: &RepoUrlParts,
2231
org_slug: T,
2332
parent_name: String,
24-
case: &quick_junit::TestCase,
25-
suite: &quick_junit::TestSuite,
33+
case: &BindingsTestCase,
34+
suite: &BindingsTestSuite,
2635
) -> Test {
2736
let name = String::from(case.name.as_str());
28-
let xml_string_to_string = |s: &quick_junit::XmlString| String::from(s.as_str());
29-
let class_name = case.classname.as_ref().map(xml_string_to_string);
30-
let file = case.extra.get("file").map(xml_string_to_string);
31-
let timestamp_millis = case
32-
.timestamp
33-
.or(suite.timestamp)
34-
.map(|t| t.timestamp_millis());
37+
let class_name = case.classname.clone();
38+
let file = case.extra().get("file").cloned();
39+
let timestamp_millis = case.timestamp.or(suite.timestamp);
3540
let mut test = Test {
3641
name,
3742
parent_name,
3843
class_name,
3944
file,
4045
id: String::with_capacity(0),
4146
timestamp_millis,
47+
is_quarantined: case.status.status == BindingsTestCaseStatusStatus::Quarantined,
4248
};
43-
if let Some(id) = case.extra.get("id").map(xml_string_to_string) {
44-
test.id = id;
49+
if let Some(id) = case.extra().get("id") {
50+
if id.is_empty() {
51+
test.set_id(org_slug, repo);
52+
} else {
53+
test.id = id.clone();
54+
}
4555
} else {
4656
test.set_id(org_slug, repo);
4757
}
@@ -65,39 +75,59 @@ impl FailedTestsExtractor {
6575
continue;
6676
}
6777
}
68-
for file in &file_set.files {
69-
let file = match std::fs::File::open(&file.original_path) {
78+
for base_file in &file_set.files {
79+
let file = match std::fs::File::open(&base_file.original_path) {
7080
Ok(file) => file,
7181
Err(e) => {
72-
tracing::warn!("Error opening file: {}", e);
7382
continue;
7483
}
7584
};
76-
let reader = std::io::BufReader::new(file);
77-
let mut junitxml = JunitParser::new();
78-
match junitxml.parse(reader) {
79-
Ok(junitxml) => junitxml,
80-
Err(e) => {
81-
tracing::warn!("Error parsing junitxml: {}", e);
85+
let mut reader = BufReader::new(file);
86+
// check if the extension ends with xml
87+
let bindings_reports = if file_set.file_set_type == FileSetType::Junit {
88+
let mut junitxml = JunitParser::new();
89+
match junitxml.parse(reader) {
90+
Ok(junitxml) => junitxml,
91+
Err(e) => {
92+
continue;
93+
}
94+
};
95+
junitxml
96+
.into_reports()
97+
.iter()
98+
.map(|report| BindingsReport::from(report.clone()))
99+
.collect::<Vec<BindingsReport>>()
100+
} else {
101+
// Create a vector to hold the contents
102+
let mut buffer = Vec::new();
103+
104+
// Read all the bytes into the vector
105+
let result = reader.read_to_end(&mut buffer);
106+
if let Err(e) = result {
82107
continue;
83108
}
109+
let test_result =
110+
proto::test_context::test_run::TestResult::decode(buffer.as_slice())
111+
.unwrap();
112+
vec![BindingsReport::from(test_result)]
84113
};
85-
for report in junitxml.reports() {
86-
for suite in &report.test_suites {
114+
for report in bindings_reports {
115+
for suite in report.test_suites {
87116
let parent_name = String::from(suite.name.as_str());
88117
for case in &suite.test_cases {
89118
let test = convert_case_to_test(
90119
repo,
91120
org_slug.as_ref(),
92121
parent_name.clone(),
93122
case,
94-
suite,
123+
&suite,
95124
);
96-
match &case.status {
97-
TestCaseStatus::Skipped { .. } => {
125+
match &case.status.status {
126+
BindingsTestCaseStatusStatus::Unspecified
127+
| BindingsTestCaseStatusStatus::Skipped { .. } => {
98128
continue;
99129
}
100-
TestCaseStatus::Success { .. } => {
130+
BindingsTestCaseStatusStatus::Success { .. } => {
101131
if let Some(existing_timestamp) = successes.get(&test.id) {
102132
if *existing_timestamp > test.timestamp_millis.unwrap_or(0)
103133
{
@@ -109,7 +139,8 @@ impl FailedTestsExtractor {
109139
test.timestamp_millis.unwrap_or(0),
110140
);
111141
}
112-
TestCaseStatus::NonSuccess { .. } => {
142+
BindingsTestCaseStatusStatus::Quarantined
143+
| BindingsTestCaseStatusStatus::NonSuccess { .. } => {
113144
// Only store the most recent failure of a given test run ID
114145
if let Some(existing_test) = failures.get(&test.id) {
115146
if existing_test.timestamp_millis > test.timestamp_millis {
@@ -179,7 +210,13 @@ pub async fn gather_quarantine_context(
179210
};
180211
}
181212

182-
let quarantine_config = if !failed_tests_extractor.failed_tests().is_empty() {
213+
let quarantine_config = if !failed_tests_extractor.failed_tests().is_empty()
214+
&& file_set_builder
215+
.file_sets()
216+
.iter()
217+
// internal files track quarantine status directly, so we don't need to check them
218+
.any(|file_set| file_set.file_set_type == FileSetType::Junit)
219+
{
183220
tracing::info!("Checking if failed tests can be quarantined");
184221
let result = api_client.get_quarantining_config(request).await;
185222

@@ -195,7 +232,7 @@ pub async fn gather_quarantine_context(
195232

196233
result.unwrap_or_default()
197234
} else {
198-
tracing::debug!("No failed tests to quarantine");
235+
tracing::debug!("Skipping quarantine check.");
199236
api::message::GetQuarantineConfigResponse::default()
200237
};
201238

@@ -223,7 +260,7 @@ pub async fn gather_quarantine_context(
223260
.iter()
224261
.cloned()
225262
.for_each(|failure| {
226-
let quarantine_failure = quarantined.contains(&failure.id);
263+
let quarantine_failure = quarantined.contains(&failure.id) || failure.is_quarantined;
227264
if quarantine_failure {
228265
quarantined_failures.push(failure);
229266
} else {

Diff for: context-ruby/Cargo.lock

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)