Skip to content

Adding New Isolated Timeout Test Class #45202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Jun 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b8f6ed8
potential fault injection fix
ibrandes May 5, 2025
54ad6a8
removing redundant class
ibrandes May 5, 2025
1765519
removing timeout from args to align with other tests that use this api
ibrandes May 5, 2025
25e44cc
making supplier match sync test, which does not have flakiness problems
ibrandes May 5, 2025
10cb93b
increasing response and read timeout again
ibrandes May 5, 2025
da074f5
wip
ibrandes May 5, 2025
703b608
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-java in…
ibrandes May 5, 2025
e9adf59
removing management plane interaction and replacing with data plane i…
ibrandes May 5, 2025
5c98dad
changing seconds to days to match uploadFromUrlDestinationRequestCond…
ibrandes May 6, 2025
fbf8977
making root tests playback only to resolve race conditions due to par…
ibrandes May 6, 2025
36604c8
removing a test that was literally never used
ibrandes May 6, 2025
57938d7
adding mocking to list resource tests
ibrandes May 12, 2025
1e1d1ff
adding recordings
ibrandes May 12, 2025
fd928ed
increasing timeouts for sync tests
ibrandes May 12, 2025
ac9451e
fixing timeout mistake in ternary operator
ibrandes May 12, 2025
e6ba874
moving timeout tests to an isolated test file
ibrandes May 13, 2025
3bb234f
removing timeout difference between sync and async
ibrandes May 13, 2025
611cfb7
removing unused method and moving variables
ibrandes May 13, 2025
e2e987f
removing another unused method, changing util method names, and reduc…
ibrandes May 14, 2025
6fb2c69
removing everything besides timeout test changes to put into another PR
ibrandes May 15, 2025
8140f2e
format fix
ibrandes May 15, 2025
6f97482
spotless
ibrandes May 15, 2025
271417a
changing assets tag back to what it was before
ibrandes May 15, 2025
8742e8d
fixing sync timeout
ibrandes May 21, 2025
2eba4e7
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-java in…
ibrandes May 21, 2025
5ec3162
refactoring timeout test class helper method
ibrandes Jun 1, 2025
66e1c22
style
ibrandes Jun 1, 2025
5ad4052
style
ibrandes Jun 1, 2025
437a19f
refactoring timeout test class helper method (x2)
ibrandes Jun 5, 2025
d076e1f
generalizing xml building methods
ibrandes Jun 11, 2025
9cbe0e4
removing instance property counters
ibrandes Jun 11, 2025
fc6956c
adding todo comment
ibrandes Jun 11, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.azure.core.http.rest.Response;
import com.azure.core.test.TestMode;
import com.azure.core.test.TestProxyTestBase;
import com.azure.core.test.http.MockHttpResponse;
import com.azure.core.test.models.CustomMatcher;
import com.azure.core.test.models.TestProxySanitizer;
import com.azure.core.test.models.TestProxySanitizerType;
Expand Down Expand Up @@ -62,11 +63,14 @@
import com.azure.storage.common.test.shared.TestDataFactory;
import com.azure.storage.common.test.shared.TestEnvironment;
import com.azure.storage.common.test.shared.policy.PerCallVersionPolicy;
import com.azure.xml.XmlWriter;
import org.junit.jupiter.params.provider.Arguments;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -82,6 +86,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -1073,4 +1079,162 @@ protected static TokenCredential getTokenCredential(TestMode testMode) {
return new MockTokenCredential();
}
}

// todo (isbr): https://github.com/Azure/azure-sdk-for-java/pull/45202#pullrequestreview-2915149773
protected static final class PagingTimeoutTestClient implements HttpClient {
private final Queue<String> responses = new java.util.LinkedList<>();

private enum PageType {
LIST_BLOBS, FIND_BLOBS, LIST_CONTAINERS
}

public PagingTimeoutTestClient() {
}

public PagingTimeoutTestClient addListBlobsResponses(int totalResourcesExpected, int maxResourcesPerPage,
boolean isHierarchical) {
return addPagedResponses(totalResourcesExpected, maxResourcesPerPage, PageType.LIST_BLOBS, isHierarchical);
}

public PagingTimeoutTestClient addFindBlobsResponses(int totalResourcesExpected, int maxResourcesPerPage) {
return addPagedResponses(totalResourcesExpected, maxResourcesPerPage, PageType.FIND_BLOBS, false);
}

public PagingTimeoutTestClient addListContainersResponses(int totalResourcesExpected, int maxResourcesPerPage) {
return addPagedResponses(totalResourcesExpected, maxResourcesPerPage, PageType.LIST_CONTAINERS, false);
}

private PagingTimeoutTestClient addPagedResponses(int totalResourcesExpected, int maxResourcesPerPage,
PageType pageType, boolean isHierarchical) {
int totalPagesExpected = (int) Math.ceil((double) totalResourcesExpected / maxResourcesPerPage);
int resourcesAdded = 0;
for (int pageNum = 0; pageNum < totalPagesExpected; pageNum++) {
int numberOfElementsOnThisPage = Math.min(maxResourcesPerPage, totalResourcesExpected - resourcesAdded);
resourcesAdded += numberOfElementsOnThisPage;

try {
responses.add(buildXmlPage(pageNum, maxResourcesPerPage, totalPagesExpected,
numberOfElementsOnThisPage, pageType, isHierarchical));
} catch (Exception e) {
throw new RuntimeException("Failed to generate XML for paged response", e);
}
}
return this;
}

private String buildXmlPage(int pageNum, int maxResourcesPerPage, int totalPagesExpected,
int numberOfElementsOnThisPage, PageType pageType, boolean isHierarchicalForBlobs) throws Exception {
ByteArrayOutputStream output = new ByteArrayOutputStream();
XmlWriter xmlWriter = XmlWriter.toStream(output);

String elementType;
Callable<Void> additionalElements = null;

switch (pageType) {
case LIST_BLOBS:
elementType = "Blob";
startXml(pageNum, xmlWriter, () -> {
xmlWriter.writeStringAttribute("ContainerName", "foo");
return null;
});
xmlWriter.writeStringElement("MaxResults", String.valueOf(maxResourcesPerPage));
if (isHierarchicalForBlobs) {
xmlWriter.writeStringElement("Delimiter", "/");
}
break;

case FIND_BLOBS:
elementType = "Blob";
additionalElements = () -> {
xmlWriter.writeStringElement("ContainerName", "foo");

// Write Tags
xmlWriter.writeStartElement("Tags");
xmlWriter.writeStartElement("TagSet");
xmlWriter.writeStartElement("Tag");
xmlWriter.writeStringElement("Key", "dummyKey");
xmlWriter.writeStringElement("Value", "dummyValue");
xmlWriter.writeEndElement(); // End Tag
xmlWriter.writeEndElement(); // End TagSet
xmlWriter.writeEndElement(); // End Tags
return null;
};
startXml(pageNum, xmlWriter, null);
xmlWriter.writeStringElement("Where", "\"dummyKey\"='dummyValue'");
xmlWriter.writeStringElement("MaxResults", String.valueOf(maxResourcesPerPage));
break;

case LIST_CONTAINERS:
elementType = "Container";
startXml(pageNum, xmlWriter, null);
xmlWriter.writeStringElement("MaxResults", String.valueOf(maxResourcesPerPage));
break;

default:
throw new IllegalArgumentException("Unknown PageType: " + pageType);
}

writeGenericListElement(xmlWriter, elementType, numberOfElementsOnThisPage, additionalElements);
endXml(pageNum, xmlWriter, totalPagesExpected); // This calls flush

return output.toString();
}

private void startXml(int pageNum, XmlWriter xmlWriter, Callable<Void> additionalAttributes) throws Exception {
xmlWriter.writeStartDocument();
xmlWriter.writeStartElement("EnumerationResults");
xmlWriter.writeStringAttribute("ServiceEndpoint", "https://account.blob.core.windows.net/");

if (additionalAttributes != null) {
additionalAttributes.call();
}

// Write marker if not first page
if (pageNum != 0) {
xmlWriter.writeStringElement("Marker", "MARKER--");
}
}

private void endXml(int pageNum, XmlWriter xmlWriter, int totalPagesExpected) throws XMLStreamException {
// Write NextMarker
if (pageNum == totalPagesExpected - 1) {
xmlWriter.writeStringElement("NextMarker", null);
} else {
xmlWriter.writeStringElement("NextMarker", "MARKER--");
}

xmlWriter.writeEndElement(); // End EnumerationResults
xmlWriter.flush();
}

private void writeGenericListElement(XmlWriter xmlWriter, String elementType, int numberOfElementsOnThisPage,
Callable<Void> additionalElements) throws Exception {
// Start elementType + s
xmlWriter.writeStartElement(elementType + "s");

// Write entries
for (int i = 0; i < numberOfElementsOnThisPage; i++) {
xmlWriter.writeStartElement(elementType); // Start elementType
xmlWriter.writeStringElement("Name", elementType.toLowerCase());

if (additionalElements != null) {
additionalElements.call();
}

xmlWriter.writeEndElement(); // End elementType
}

xmlWriter.writeEndElement(); // End elementType + s
}

@Override
public Mono<HttpResponse> send(HttpRequest request) {
HttpHeaders headers = new HttpHeaders().set(HttpHeaderName.CONTENT_TYPE, "application/xml");
HttpResponse response
= new MockHttpResponse(request, 200, headers, responses.poll().getBytes(StandardCharsets.UTF_8));

int requestDelaySeconds = 4;
return Mono.delay(Duration.ofSeconds(requestDelaySeconds)).then(Mono.just(response));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@

import java.io.ByteArrayInputStream;
import java.net.URL;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Base64;
Expand Down Expand Up @@ -1070,40 +1069,6 @@ public void listBlobsFlatError() {
assertThrows(BlobStorageException.class, () -> cc.listBlobs().iterator().hasNext());
}

@Test
public void listBlobsFlatWithTimeoutStillBackedByPagedStream() {
int numBlobs = 5;
int pageResults = 3;

for (int i = 0; i < numBlobs; i++) {
BlockBlobClient blob = cc.getBlobClient(generateBlobName()).getBlockBlobClient();
blob.upload(DATA.getDefaultInputStream(), DATA.getDefaultDataSize());
}

// when: "Consume results by page, then still have paging functionality"
assertDoesNotThrow(
() -> cc.listBlobs(new ListBlobsOptions().setMaxResultsPerPage(pageResults), Duration.ofSeconds(10))
.streamByPage()
.count());
}

@Test
public void listBlobsHierWithTimeoutStillBackedByPagedStream() {
int numBlobs = 5;
int pageResults = 3;

for (int i = 0; i < numBlobs; i++) {
BlockBlobClient blob = cc.getBlobClient(generateBlobName()).getBlockBlobClient();
blob.upload(DATA.getDefaultInputStream(), DATA.getDefaultDataSize());
}

// when: "Consume results by page, then still have paging functionality"
assertDoesNotThrow(() -> cc
.listBlobsByHierarchy("/", new ListBlobsOptions().setMaxResultsPerPage(pageResults), Duration.ofSeconds(10))
.streamByPage()
.count());
}

/*
This test requires two accounts that are configured in a very specific way. It is not feasible to setup that
relationship programmatically, so we have recorded a successful interaction and only test recordings.
Expand Down Expand Up @@ -1656,28 +1621,6 @@ public void findBlobsError() {
assertThrows(BlobStorageException.class, () -> cc.findBlobsByTags("garbageTag").streamByPage().count());
}

@SuppressWarnings("deprecation")
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2021-04-10")
@Test
public void findBlobsWithTimeoutStillBackedByPagedStream() {
int numBlobs = 5;
int pageResults = 3;
Map<String, String> tags = Collections.singletonMap(tagKey, tagValue);

for (int i = 0; i < numBlobs; i++) {
cc.getBlobClient(generateBlobName())
.uploadWithResponse(
new BlobParallelUploadOptions(DATA.getDefaultInputStream(), DATA.getDefaultDataSize())
.setTags(tags),
null, null);
}

// when: "Consume results by page, still have paging functionality"
assertDoesNotThrow(() -> cc.findBlobsByTags(
new FindBlobsOptions(String.format("\"%s\"='%s'", tagKey, tagValue)).setMaxResultsPerPage(pageResults),
Duration.ofSeconds(10), Context.NONE).streamByPage().count());
}

@ParameterizedTest
@ValueSource(strings = { "中文", "az[]", "hello world", "hello/world", "hello&world", "!*'();:@&=+/$,/?#[]" })
public void createURLSpecialChars(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1122,42 +1122,6 @@ public void listBlobsFlatError() {
StepVerifier.create(ccAsync.listBlobs()).verifyError(BlobStorageException.class);
}

@Test
public void listBlobsFlatWithTimeoutStillBackedByPagedFlux() {
int numBlobs = 5;
int pageResults = 3;

Mono<List<BlockBlobItem>> createBlob = Flux.range(0, numBlobs).flatMap(i -> {
BlockBlobAsyncClient blob = ccAsync.getBlobAsyncClient(generateBlobName()).getBlockBlobAsyncClient();
return blob.upload(DATA.getDefaultFlux(), DATA.getDefaultDataSize());
}).collectList();

// when: "Consume results by page, then still have paging functionality"
StepVerifier
.create(createBlob
.thenMany(ccAsync.listBlobs(new ListBlobsOptions().setMaxResultsPerPage(pageResults)).byPage()))
.expectNextCount(2)
.verifyComplete();
}

@Test
public void listBlobsHierWithTimeoutStillBackedByPagedFlux() {
int numBlobs = 5;
int pageResults = 3;

Mono<List<BlockBlobItem>> createBlob = Flux.range(0, numBlobs).flatMap(i -> {
BlockBlobAsyncClient blob = ccAsync.getBlobAsyncClient(generateBlobName()).getBlockBlobAsyncClient();
return blob.upload(DATA.getDefaultFlux(), DATA.getDefaultDataSize());
}).collectList();

// when: "Consume results by page, then still have paging functionality"
StepVerifier
.create(createBlob.thenMany(
ccAsync.listBlobsByHierarchy("/", new ListBlobsOptions().setMaxResultsPerPage(pageResults)).byPage()))
.expectNextCount(2)
.verifyComplete();
}

/*
This test requires two accounts that are configured in a very specific way. It is not feasible to setup that
relationship programmatically, so we have recorded a successful interaction and only test recordings.
Expand Down Expand Up @@ -1776,35 +1740,6 @@ public void findBlobsError() {
StepVerifier.create(ccAsync.findBlobsByTags("garbageTag").byPage()).verifyError(BlobStorageException.class);
}

@SuppressWarnings("deprecation")
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2021-04-10")
@Test
public void findBlobsWithTimeoutStillBackedByPagedFlux() {
int numBlobs = 5;
int pageResults = 3;
Map<String, String> tags = Collections.singletonMap(tagKey, tagValue);

Mono<List<Response<BlockBlobItem>>> uploadBlob = Flux.range(0, numBlobs)
.flatMap(i -> ccAsync.getBlobAsyncClient(generateBlobName())
.uploadWithResponse(
new BlobParallelUploadOptions(DATA.getDefaultInputStream(), DATA.getDefaultDataSize())
.setTags(tags)))
.collectList();

// when: "Consume results by page, still have paging functionality"
StepVerifier
.create(
uploadBlob
.thenMany(
ccAsync
.findBlobsByTags(new FindBlobsOptions(String.format("\"%s\"='%s'", tagKey, tagValue))
.setMaxResultsPerPage(pageResults), Duration.ofSeconds(10), Context.NONE)
.byPage()
.count()))
.expectNextCount(1)
.verifyComplete();
}

@ParameterizedTest
@ValueSource(strings = { "中文", "az[]", "hello world", "hello/world", "hello&world", "!*'();:@&=+/$,/?#[]" })
public void createURLSpecialChars(String name) {
Expand Down
Loading
Loading