Skip to content

Commit 6373a18

Browse files
authored
Compress files into a single coverage.zip and update upload logic accordingly (#1528)
Instead of uploading 4 separate files for coverage uploads, this uploads a single zip file containing the 4 files. This cannot be merged until a server side component which knows how to read the single zip file is merged.
1 parent 2ed72a1 commit 6373a18

File tree

4 files changed

+112
-96
lines changed

4 files changed

+112
-96
lines changed

Cargo.lock

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

qlty-cloud/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ ureq.workspace = true
3232
uuid.workspace = true
3333
webbrowser.workspace = true
3434
zip.workspace = true
35+
36+
[dev-dependencies]
37+
tempfile = "3.2"

qlty-cloud/src/export/coverage.rs

+100-28
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use crate::format::{GzFormatter, JsonEachRowFormatter, JsonFormatter};
1+
use crate::format::{JsonEachRowFormatter, JsonFormatter};
22
use anyhow::{Context, Result};
33
use qlty_types::tests::v1::{CoverageMetadata, FileCoverage, ReportFile};
4+
use std::collections::HashMap;
45
use std::fs::File;
56
use std::io::Read;
67
use std::path::{Path, PathBuf};
78
use zip::{write::FileOptions, ZipWriter};
89

9-
fn compress_files(files: Vec<String>, output_file: &Path) -> Result<()> {
10+
fn compress_files(files: HashMap<String, PathBuf>, output_file: &Path) -> Result<()> {
1011
// Create the output ZIP file
1112
let zip_file = File::create(output_file)?;
1213
let mut zip = ZipWriter::new(zip_file);
@@ -15,20 +16,16 @@ fn compress_files(files: Vec<String>, output_file: &Path) -> Result<()> {
1516
.compression_method(zip::CompressionMethod::Deflated) // Compression method
1617
.unix_permissions(0o755);
1718

18-
// Iterate over the list of files to compress
19-
for file_path in files {
20-
let path = Path::new(&file_path);
21-
22-
if path.is_file() {
19+
for (name, file_path) in &files {
20+
if file_path.is_file() {
2321
// Add the file to the archive
24-
// Use path as filename in case multiple files with same name
25-
zip.start_file(path.to_string_lossy(), options)?;
22+
zip.start_file(name, options)?;
2623

2724
// Write the file content to the archive
28-
let mut file = File::open(path)?;
25+
let mut file = File::open(file_path)?;
2926
std::io::copy(&mut file, &mut zip)?;
3027
} else {
31-
eprintln!("Skipping non-file: {}", file_path);
28+
eprintln!("Skipping non-file: {}", file_path.to_string_lossy());
3229
}
3330
}
3431

@@ -54,34 +51,44 @@ impl CoverageExport {
5451
fn export(&self) -> Result<()> {
5552
let directory = self.to.as_ref().unwrap();
5653

57-
GzFormatter::new(JsonEachRowFormatter::new(self.report_files.clone()))
58-
.write_to_file(&directory.join("report_files.json.gz"))?;
54+
JsonEachRowFormatter::new(self.report_files.clone())
55+
.write_to_file(&directory.join("report_files.jsonl"))?;
5956

60-
GzFormatter::new(JsonEachRowFormatter::new(self.file_coverages.clone()))
61-
.write_to_file(&directory.join("file_coverages.json.gz"))?;
57+
JsonEachRowFormatter::new(self.file_coverages.clone())
58+
.write_to_file(&directory.join("file_coverages.jsonl"))?;
6259

6360
JsonFormatter::new(self.metadata.clone())
6461
.write_to_file(&directory.join("metadata.json"))?;
6562

66-
let raw_file_paths = self
67-
.report_files
68-
.iter()
69-
.map(|report_file| &report_file.path)
70-
.cloned()
71-
.collect();
63+
let zip_file_contents = self.compute_zip_file_contents(directory)?;
7264

73-
compress_files(raw_file_paths, &directory.join("raw_files.zip"))
65+
compress_files(zip_file_contents, &directory.join("coverage.zip"))
7466
}
7567

7668
pub fn total_size_bytes(&self) -> Result<u64> {
77-
let mut bytes: u64 = 0;
69+
Ok(self.read_file("coverage.zip")?.len() as u64)
70+
}
7871

79-
bytes += self.read_file("report_files.json.gz")?.len() as u64;
80-
bytes += self.read_file("file_coverages.json.gz")?.len() as u64;
81-
bytes += self.read_file("metadata.json")?.len() as u64;
82-
bytes += self.read_file("raw_files.zip")?.len() as u64;
72+
fn compute_zip_file_contents(&self, directory: &Path) -> Result<HashMap<String, PathBuf>> {
73+
let mut files_to_zip = HashMap::new();
74+
75+
files_to_zip.insert(
76+
"report_files.jsonl".to_string(),
77+
directory.join("report_files.jsonl"),
78+
);
79+
files_to_zip.insert(
80+
"file_coverages.jsonl".to_string(),
81+
directory.join("file_coverages.jsonl"),
82+
);
83+
files_to_zip.insert("metadata.json".to_string(), directory.join("metadata.json"));
84+
85+
for report_file in &self.report_files {
86+
let actual_path = PathBuf::from(&report_file.path);
87+
let zip_file_name = PathBuf::from("raw_files").join(&report_file.path);
88+
files_to_zip.insert(zip_file_name.to_string_lossy().into_owned(), actual_path);
89+
}
8390

84-
Ok(bytes)
91+
Ok(files_to_zip)
8592
}
8693

8794
pub fn read_file<P: AsRef<Path>>(&self, filename: P) -> Result<Vec<u8>> {
@@ -97,3 +104,68 @@ impl CoverageExport {
97104
Ok(buffer)
98105
}
99106
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
use std::io::Write;
112+
use tempfile::{tempdir, TempDir};
113+
use zip::read::ZipArchive;
114+
115+
#[test]
116+
fn test_export_to() {
117+
let destination_binding = tempdir().unwrap();
118+
let destination = destination_binding.path();
119+
120+
let raw_files_temp_binding = TempDir::new_in(".").unwrap();
121+
let raw_files_dir = raw_files_temp_binding.path();
122+
123+
let f1 = &raw_files_dir.join("coverage.lcov");
124+
let mut file = File::create(f1).unwrap();
125+
writeln!(file, "D").unwrap();
126+
127+
let metadata = CoverageMetadata::default();
128+
let relative_raw_file_path = raw_files_dir
129+
.file_name()
130+
.map(|name| {
131+
Path::new(name)
132+
.join("coverage.lcov")
133+
.to_string_lossy()
134+
.into_owned()
135+
})
136+
.unwrap_or_default();
137+
138+
// the ReportFile path is what is supplied by a user as one of the path arguments to `qlty coverage publish path/to/coverage.lcov`
139+
// that path could be relative or absolute (but probably more typically relative)
140+
let report_files = vec![ReportFile {
141+
path: relative_raw_file_path.clone(),
142+
..Default::default()
143+
}];
144+
let file_coverages = vec![FileCoverage::default()];
145+
146+
let mut export = CoverageExport {
147+
metadata,
148+
report_files,
149+
file_coverages,
150+
to: None,
151+
};
152+
153+
export.export_to(Some(destination.to_path_buf())).unwrap();
154+
155+
assert!(destination.join("coverage.zip").exists());
156+
157+
// Verify the contents of the zip file
158+
let zip_file = File::open(destination.join("coverage.zip")).unwrap();
159+
let mut zip = ZipArchive::new(zip_file).unwrap();
160+
161+
assert!(zip.by_name("report_files.jsonl").is_ok());
162+
assert!(zip.by_name("file_coverages.jsonl").is_ok());
163+
assert!(zip.by_name("metadata.json").is_ok());
164+
let raw_file_path = PathBuf::from("raw_files")
165+
.join(raw_files_dir.file_name().unwrap())
166+
.join("coverage.lcov");
167+
assert!(zip
168+
.by_name(raw_file_path.to_string_lossy().into_owned().as_str())
169+
.is_ok());
170+
}
171+
}

qlty-coverage/src/publish/upload.rs

+8-68
Original file line numberDiff line numberDiff line change
@@ -12,63 +12,24 @@ const LEGACY_API_URL: &str = "https://qlty.sh/api";
1212
pub struct Upload {
1313
pub id: String,
1414
pub project_id: String,
15-
pub file_coverages_url: String,
16-
pub report_files_url: String,
17-
pub metadata_url: String,
18-
pub raw_files_url: String,
15+
pub coverage_url: String,
1916
}
2017

2118
impl Upload {
2219
pub fn prepare(token: &str, report: &mut Report) -> Result<Self> {
2320
let response = Self::request_api(&report.metadata, token)?;
2421

25-
let file_coverages_url = response
22+
let coverage_url = response
2623
.get("data")
27-
.and_then(|data| data.get("file_coverages.json.gz"))
24+
.and_then(|data| data.get("coverage.zip"))
2825
.and_then(|upload_url| upload_url.as_str())
2926
.with_context(|| {
3027
format!(
31-
"Unable to find file coverages URL in response body: {:?}",
28+
"Unable to find coverage URL in response body: {:?}",
3229
response
3330
)
3431
})
35-
.context("Failed to extract file coverages URL from response")?;
36-
37-
let report_files_url = response
38-
.get("data")
39-
.and_then(|data| data.get("report_files.json.gz"))
40-
.and_then(|upload_url| upload_url.as_str())
41-
.with_context(|| {
42-
format!(
43-
"Unable to find report files URL in response body: {:?}",
44-
response
45-
)
46-
})
47-
.context("Failed to extract report files URL from response")?;
48-
49-
let metadata_url = response
50-
.get("data")
51-
.and_then(|data| data.get("metadata.json"))
52-
.and_then(|upload_url| upload_url.as_str())
53-
.with_context(|| {
54-
format!(
55-
"Unable to find metadata URL in response body: {:?}",
56-
response
57-
)
58-
})
59-
.context("Failed to extract metadata URL from response")?;
60-
61-
let raw_files_url = response
62-
.get("data")
63-
.and_then(|data| data.get("raw_files.zip"))
64-
.and_then(|upload_url| upload_url.as_str())
65-
.with_context(|| {
66-
format!(
67-
"Unable to find metadata URL in response body: {:?}",
68-
response
69-
)
70-
})
71-
.context("Failed to extract metadata URL from response")?;
32+
.context("Failed to extract coverage URL from response")?;
7233

7334
let id = response
7435
.get("data")
@@ -90,36 +51,15 @@ impl Upload {
9051
Ok(Self {
9152
id: id.to_string(),
9253
project_id: project_id.to_string(),
93-
file_coverages_url: file_coverages_url.to_string(),
94-
report_files_url: report_files_url.to_string(),
95-
metadata_url: metadata_url.to_string(),
96-
raw_files_url: raw_files_url.to_string(),
54+
coverage_url: coverage_url.to_string(),
9755
})
9856
}
9957

10058
pub fn upload(&self, export: &CoverageExport) -> Result<()> {
10159
self.upload_data(
102-
&self.file_coverages_url,
103-
"application/gzip",
104-
export.read_file(PathBuf::from("file_coverages.json.gz"))?,
105-
)?;
106-
107-
self.upload_data(
108-
&self.report_files_url,
109-
"application/gzip",
110-
export.read_file(PathBuf::from("report_files.json.gz"))?,
111-
)?;
112-
113-
self.upload_data(
114-
&self.metadata_url,
115-
"application/json",
116-
export.read_file(PathBuf::from("metadata.json"))?,
117-
)?;
118-
119-
self.upload_data(
120-
&self.raw_files_url,
60+
&self.coverage_url,
12161
"application/zip",
122-
export.read_file(PathBuf::from("raw_files.zip"))?,
62+
export.read_file(PathBuf::from("coverage.zip"))?,
12363
)?;
12464

12565
Ok(())

0 commit comments

Comments
 (0)