Skip to content

Commit

Permalink
test(proguard): Add chunk upload tests
Browse files Browse the repository at this point in the history
Add tests for chunk uploading Proguard mappings.

Test the following scenarios:
- Uploading a single Proguard mapping, where the file is already on the server.
- Uploading a single Proguard mapping, where the file is not on the server.
- Uploading two Proguard mappings, where neither is on the server.
  • Loading branch information
szokeasaurusrex committed Dec 18, 2024
1 parent 4a6ea5d commit e8e04f4
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 0 deletions.
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions tests/integration/_fixtures/upload_proguard/mapping-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HelloWorld2 -> HelloWorld2:
# {"fileName":"HelloWorld2.java","id":"sourceFile"}
1:1:void <init>() -> <init>
3:4:void main(java.lang.String[]) -> main
4 changes: 4 additions & 0 deletions tests/integration/_fixtures/upload_proguard/mapping.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HelloWorld -> HelloWorld:
# {"fileName":"HelloWorld.java","id":"sourceFile"}
1:1:void <init>() -> <init>
3:4:void main(java.lang.String[]) -> main
11 changes: 11 additions & 0 deletions tests/integration/_responses/upload_proguard/get-chunk-upload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"url": "organizations/wat-org/chunk-upload/",
"chunkSize": 8388608,
"chunksPerRequest": 64,
"maxFileSize": 2147483648,
"maxRequestSize": 33554432,
"concurrency": 8,
"hashAlgorithm": "sha1",
"compression": ["gzip"],
"accept": ["debug_files", "release_files", "pdbs", "portablepdbs", "sources", "bcsymbolmaps","proguard"]
}
6 changes: 6 additions & 0 deletions tests/integration/test_utils/test_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ impl AssertCmdTestManager {
self
}

/// Set a custom environment variable for the test.
pub fn env(mut self, variable: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self {
self.command.env(variable, value);
self
}

/// Run the command and perform assertions.
///
/// This function asserts both the mocks and the command result.
Expand Down
344 changes: 344 additions & 0 deletions tests/integration/upload_proguard.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
use std::sync::atomic::{AtomicU8, Ordering};
use std::{fs, str};

use mockito::Matcher;

use crate::integration::test_utils::{chunk_upload, AssertCommand};
use crate::integration::{MockEndpointBuilder, TestManager};

#[test]
Expand All @@ -15,3 +21,341 @@ fn command_upload_proguard() {
fn command_upload_proguard_no_upload_no_auth_token() {
TestManager::new().register_trycmd_test("upload_proguard/upload_proguard-no-upload.trycmd");
}

#[test]
fn chunk_upload_already_there() {
TestManager::new()
.mock_endpoint(
MockEndpointBuilder::new("GET", "/api/0/organizations/wat-org/chunk-upload/")
.with_response_file("debug_files/get-chunk-upload.json"),
)
.mock_endpoint(
MockEndpointBuilder::new(
"POST",
"/api/0/projects/wat-org/wat-project/files/difs/assemble/",
)
.with_header_matcher("content-type", "application/json")
.with_matcher(
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"name\":\"/proguard/c038584d-c366-570c-ad1e-034fa0d194d7.txt\",\
\"chunks\":[\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\"]\
}\
}",
)
.with_response_body(
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"ok\",\
\"detail\":null,\
\"missingChunks\":[],\
\"dif\":{\
\"id\":\"12\",\
\"uuid\":\"c038584d-c366-570c-ad1e-034fa0d194d7\",\
\"debugId\":\"c038584d-c366-570c-ad1e-034fa0d194d7\",\
\"codeId\":null,\
\"cpuName\":\"any\",\
\"objectName\":\"proguard-mapping\",\
\"symbolType\":\"proguard\",\
\"headers\":{\"Content-Type\":\"text/x-proguard+plain\"},\
\"size\":155,\
\"sha1\":\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\",\
\"dateCreated\":\"1776-07-04T12:00:00.000Z\",\
\"data\":{\"features\":[\"mapping\"]}\
}\
}\
}",
),
)
.assert_cmd([
"upload-proguard",
"tests/integration/_fixtures/upload_proguard/mapping.txt",
])
.with_default_token()
.env("SENTRY_EXPERIMENTAL_PROGUARD_CHUNK_UPLOAD", "1")
.run_and_assert(AssertCommand::Success)
}

#[test]
fn chunk_upload_needs_upload() {
const EXPECTED_CHUNKS_BOUNDARY: &str = "------------------------w2uOUUnuLEYTmQorc0ix48";

let call_count = AtomicU8::new(0);
let expected_chunk_body = fs::read(
"tests/integration/_expected_requests/upload_proguard/chunk_upload_needs_upload.bin",
)
.expect("expected chunk upload request file should be readable");

TestManager::new()
.mock_endpoint(
MockEndpointBuilder::new("GET", "/api/0/organizations/wat-org/chunk-upload/")
.with_response_file("debug_files/get-chunk-upload.json"),
)
.mock_endpoint(
MockEndpointBuilder::new("POST", "/api/0/organizations/wat-org/chunk-upload/")
.with_response_fn(move |request| {
let boundary = chunk_upload::boundary_from_request(request)
.expect("content-type header should be a valid multipart/form-data header");

let body = request.body().expect("body should be readable");

let chunks = chunk_upload::split_chunk_body(body, boundary)
.expect("body should be a valid multipart/form-data body");

let expected_chunks = chunk_upload::split_chunk_body(
&expected_chunk_body,
EXPECTED_CHUNKS_BOUNDARY,
)
.expect("expected body is valid multipart form data");

// Using assert! because in case of failure, the output with assert_eq!
// is too long to be useful.
assert_eq!(
chunks, expected_chunks,
"Uploaded chunks differ from the expected chunks"
);

vec![]
}),
)
.mock_endpoint(
MockEndpointBuilder::new(
"POST",
"/api/0/projects/wat-org/wat-project/files/difs/assemble/",
)
.with_header_matcher("content-type", "application/json")
.with_matcher(
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"name\":\"/proguard/c038584d-c366-570c-ad1e-034fa0d194d7.txt\",\
\"chunks\":[\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\"]\
}\
}",
)
.with_response_fn(move |_| {
match call_count.fetch_add(1, Ordering::Relaxed) {
0 => {
// First call: The file is not found since it still needs to be uploaded.
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"not_found\",\
\"missingChunks\":[\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\"]\
}\
}"
}
1 => {
// Second call: The file has been uploaded, assemble job created.
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"created\",\
\"missingChunks\":[]\
}\
}"
}
2 => {
// Third call: Assembly completed
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"ok\",\
\"detail\":null,\
\"missingChunks\":[],\
\"dif\":{\
\"id\":\"12\",\
\"uuid\":\"c038584d-c366-570c-ad1e-034fa0d194d7\",\
\"debugId\":\"c038584d-c366-570c-ad1e-034fa0d194d7\",\
\"codeId\":null,\
\"cpuName\":\"any\",\
\"objectName\":\"proguard-mapping\",\
\"symbolType\":\"proguard\",\
\"headers\":{\"Content-Type\":\"text/x-proguard+plain\"},\
\"size\":155,\
\"sha1\":\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\",\
\"dateCreated\":\"1776-07-04T12:00:00.000Z\",\
\"data\":{\"features\":[\"mapping\"]}\
}\
}\
}"
}
n => panic!(
"Only 3 calls to the assemble endpoint expected, but there were {}.",
n + 1
),
}
.into()
})
.expect(3),
)
.assert_cmd([
"upload-proguard",
"tests/integration/_fixtures/upload_proguard/mapping.txt",
])
.with_default_token()
.env("SENTRY_EXPERIMENTAL_PROGUARD_CHUNK_UPLOAD", "1")
.run_and_assert(AssertCommand::Success)
}

#[test]
fn chunk_upload_two_files() {
const EXPECTED_CHUNKS_BOUNDARY: &str = "------------------------HNdDRjCgjkRtu3COUTCcJV";

let call_count = AtomicU8::new(0);
let expected_chunk_body =
fs::read("tests/integration/_expected_requests/upload_proguard/chunk_upload_two_files.bin")
.expect("expected chunk upload request file should be readable");

TestManager::new()
.mock_endpoint(
MockEndpointBuilder::new("GET", "/api/0/organizations/wat-org/chunk-upload/")
.with_response_file("debug_files/get-chunk-upload.json"),
)
.mock_endpoint(
MockEndpointBuilder::new("POST", "/api/0/organizations/wat-org/chunk-upload/")
.with_response_fn(move |request| {
let boundary = chunk_upload::boundary_from_request(request)
.expect("content-type header should be a valid multipart/form-data header");

let body = request.body().expect("body should be readable");

let chunks = chunk_upload::split_chunk_body(body, boundary)
.expect("body should be a valid multipart/form-data body");

let expected_chunks = chunk_upload::split_chunk_body(
&expected_chunk_body,
EXPECTED_CHUNKS_BOUNDARY,
)
.expect("expected body is valid multipart form data");

// Using assert! because in case of failure, the output with assert_eq!
// is too long to be useful.
assert_eq!(
chunks, expected_chunks,
"Uploaded chunks differ from the expected chunks"
);

vec![]
}),
)
.mock_endpoint(
MockEndpointBuilder::new(
"POST",
"/api/0/projects/wat-org/wat-project/files/difs/assemble/",
)
.with_header_matcher("content-type", "application/json")
.with_matcher(Matcher::AnyOf(
// The ordering of the two files in the assemble request changes.
// Here, we match on either ordering.
[
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"name\":\"/proguard/c038584d-c366-570c-ad1e-034fa0d194d7.txt\",\
\"chunks\":[\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\"]\
},\
\"e5329624a8d06e084941f133c75b5874f793ee7c\":{\
\"name\":\"/proguard/747e1d76-509b-5225-8a5b-db7b7d4067d4.txt\",\
\"chunks\":[\"e5329624a8d06e084941f133c75b5874f793ee7c\"]\
}\
}"
.into(),
"{\
\"e5329624a8d06e084941f133c75b5874f793ee7c\":{\
\"name\":\"/proguard/747e1d76-509b-5225-8a5b-db7b7d4067d4.txt\",\
\"chunks\":[\"e5329624a8d06e084941f133c75b5874f793ee7c\"]\
},\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"name\":\"/proguard/c038584d-c366-570c-ad1e-034fa0d194d7.txt\",\
\"chunks\":[\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\"]\
}\
}"
.into(),
]
.into(),
))
.with_response_fn(move |_| {
match call_count.fetch_add(1, Ordering::Relaxed) {
0 => {
// First call: The file is not found since it still needs to be uploaded.
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"not_found\",\
\"missingChunks\":[\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\"]\
},\
\"e5329624a8d06e084941f133c75b5874f793ee7c\":{\
\"state\":\"not_found\",\
\"missingChunks\":[\"e5329624a8d06e084941f133c75b5874f793ee7c\"]\
}\
}"
}
1 => {
// Second call: The file has been uploaded, assemble job created.
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"created\",\
\"missingChunks\":[]\
},\
\"e5329624a8d06e084941f133c75b5874f793ee7c\":{\
\"state\":\"created\",\
\"missingChunks\":[]\
}\
}"
}
2 => {
// Third call: Assembly completed
"{\
\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\":{\
\"state\":\"ok\",\
\"detail\":null,\
\"missingChunks\":[],\
\"dif\":{\
\"id\":\"12\",\
\"uuid\":\"c038584d-c366-570c-ad1e-034fa0d194d7\",\
\"debugId\":\"c038584d-c366-570c-ad1e-034fa0d194d7\",\
\"codeId\":null,\
\"cpuName\":\"any\",\
\"objectName\":\"proguard-mapping\",\
\"symbolType\":\"proguard\",\
\"headers\":{\"Content-Type\":\"text/x-proguard+plain\"},\
\"size\":155,\
\"sha1\":\"297ecd9143fc2882e4b6758c1ccd13ea82930eeb\",\
\"dateCreated\":\"1776-07-04T12:00:00.000Z\",\
\"data\":{\"features\":[\"mapping\"]}\
}\
},\
\"e5329624a8d06e084941f133c75b5874f793ee7c\":{\
\"state\":\"ok\",\
\"detail\":null,\
\"missingChunks\":[],\
\"dif\":{\
\"id\":\"13\",\
\"uuid\":\"747e1d76-509b-5225-8a5b-db7b7d4067d4\",\
\"debugId\":\"747e1d76-509b-5225-8a5b-db7b7d4067d4\",\
\"codeId\":null,\
\"cpuName\":\"any\",\
\"objectName\":\"proguard-mapping\",\
\"symbolType\":\"proguard\",\
\"headers\":{\"Content-Type\":\"text/x-proguard+plain\"},\
\"size\":158,\
\"sha1\":\"e5329624a8d06e084941f133c75b5874f793ee7c\",\
\"dateCreated\":\"1776-07-04T12:00:00.000Z\",\
\"data\":{\"features\":[\"mapping\"]}\
}\
}\
}"
}
n => panic!(
"Only 3 calls to the assemble endpoint expected, but there were {}.",
n + 1
),
}
.into()
})
.expect(3),
)
.assert_cmd([
"upload-proguard",
"tests/integration/_fixtures/upload_proguard/mapping.txt",
"tests/integration/_fixtures/upload_proguard/mapping-2.txt",
])
.with_default_token()
.env("SENTRY_EXPERIMENTAL_PROGUARD_CHUNK_UPLOAD", "1")
.run_and_assert(AssertCommand::Success)
}

0 comments on commit e8e04f4

Please sign in to comment.