This is an automated email from the ASF dual-hosted git repository.

rohangarg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new ae4ea51352b Rewrite S3StorageConnectorTest using testcontainers and 
MinIO (#17539)
ae4ea51352b is described below

commit ae4ea51352bc6d0a6b9d6078478b516aee36a073
Author: Rohan Garg <[email protected]>
AuthorDate: Mon Dec 9 09:48:38 2024 -0500

    Rewrite S3StorageConnectorTest using testcontainers and MinIO (#17539)
---
 .github/scripts/unit_tests_script.sh               |   2 +-
 extensions-core/s3-extensions/pom.xml              |  44 ++
 .../storage/s3/ServerSideEncryptingAmazonS3.java   |   5 +
 .../storage/s3/output/S3StorageConnector.java      |   4 +-
 .../storage/s3/output/S3StorageConnectorTest.java  | 449 +++++++++++----------
 pom.xml                                            |  15 +
 6 files changed, 303 insertions(+), 216 deletions(-)

diff --git a/.github/scripts/unit_tests_script.sh 
b/.github/scripts/unit_tests_script.sh
index e048a19c4af..28de3e94379 100755
--- a/.github/scripts/unit_tests_script.sh
+++ b/.github/scripts/unit_tests_script.sh
@@ -22,7 +22,7 @@ unset _JAVA_OPTIONS
 # Set MAVEN_OPTS for Surefire launcher.
 MAVEN_OPTS='-Xmx2500m' ${MVN} test -pl ${MAVEN_PROJECTS} \
 ${MAVEN_SKIP} 
-Ddruid.generic.useDefaultValueForNull=${DRUID_USE_DEFAULT_VALUE_FOR_NULL} \
--DjfrProfilerArgLine="${JFR_PROFILER_ARG_LINE}"
+-DjfrProfilerArgLine="${JFR_PROFILER_ARG_LINE}" -Pci
 sh -c "dmesg | egrep -i '(oom|out of memory|kill process|killed).*' -C 1 || 
exit 0"
 free -m
 ${MVN} -pl ${MAVEN_PROJECTS} jacoco:report || { echo "coverage_failure=false" 
>> "$GITHUB_ENV" && false; }
diff --git a/extensions-core/s3-extensions/pom.xml 
b/extensions-core/s3-extensions/pom.xml
index b50d9c01a9e..cb9a03a5bae 100644
--- a/extensions-core/s3-extensions/pom.xml
+++ b/extensions-core/s3-extensions/pom.xml
@@ -159,6 +159,50 @@
       <artifactId>equalsverifier</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.testcontainers</groupId>
+      <artifactId>testcontainers</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.testcontainers</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.testcontainers</groupId>
+      <artifactId>minio</artifactId>
+      <version>1.19.5</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
+  <profiles>
+    <profile>
+      <id>local</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <groups>!requires-dockerd</groups>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
+      <id>ci</id>
+    </profile>
+  </profiles>
+
 </project>
diff --git 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java
 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java
index 0fb93a20833..31120ba883c 100644
--- 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java
+++ 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java
@@ -72,6 +72,11 @@ public class ServerSideEncryptingAmazonS3
     this.serverSideEncryption = serverSideEncryption;
   }
 
+  public AmazonS3 getAmazonS3()
+  {
+    return amazonS3;
+  }
+
   public boolean doesObjectExist(String bucket, String objectName)
   {
     try {
diff --git 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/S3StorageConnector.java
 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/S3StorageConnector.java
index 8eb391a24d3..9ea0e7a8ae0 100644
--- 
a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/S3StorageConnector.java
+++ 
b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/S3StorageConnector.java
@@ -57,8 +57,8 @@ public class S3StorageConnector extends 
ChunkingStorageConnector<GetObjectReques
   private final ServerSideEncryptingAmazonS3 s3Client;
   private final S3UploadManager s3UploadManager;
 
-  private static final String DELIM = "/";
-  private static final Joiner JOINER = Joiner.on(DELIM).skipNulls();
+  static final String DELIM = "/";
+  static final Joiner JOINER = Joiner.on(DELIM).skipNulls();
   private static final int MAX_NUMBER_OF_LISTINGS = 1000;
 
   public S3StorageConnector(S3OutputConfig config, 
ServerSideEncryptingAmazonS3 serverSideEncryptingAmazonS3, S3UploadManager 
s3UploadManager)
diff --git 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
index 68eaca1c42a..56df24d2115 100644
--- 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
+++ 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
@@ -19,160 +19,147 @@
 
 package org.apache.druid.storage.s3.output;
 
+import com.amazonaws.ClientConfigurationFactory;
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.internal.StaticCredentialsProvider;
 import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
 import com.amazonaws.services.s3.model.AmazonS3Exception;
-import com.amazonaws.services.s3.model.DeleteObjectsRequest;
-import com.amazonaws.services.s3.model.GetObjectMetadataRequest;
-import com.amazonaws.services.s3.model.GetObjectRequest;
-import com.amazonaws.services.s3.model.ListObjectsV2Request;
-import com.amazonaws.services.s3.model.ListObjectsV2Result;
-import com.amazonaws.services.s3.model.ObjectMetadata;
-import com.amazonaws.services.s3.model.S3Object;
-import com.amazonaws.services.s3.model.S3ObjectSummary;
+import com.amazonaws.services.s3.model.CreateBucketRequest;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import org.apache.druid.java.util.common.HumanReadableBytes;
+import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.metrics.StubServiceEmitter;
 import org.apache.druid.query.DruidProcessingConfigTest;
 import org.apache.druid.storage.StorageConnector;
-import org.apache.druid.storage.s3.NoopServerSideEncryption;
 import org.apache.druid.storage.s3.ServerSideEncryptingAmazonS3;
-import org.easymock.Capture;
-import org.easymock.EasyMock;
-import org.hamcrest.CoreMatchers;
-import org.hamcrest.MatcherAssert;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.internal.matchers.ThrowableCauseMatcher;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.testcontainers.containers.MinIOContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.UUID;
 
+import static org.apache.druid.storage.s3.output.S3StorageConnector.JOINER;
+
+@Testcontainers
+@Tag("requires-dockerd")
 public class S3StorageConnectorTest
 {
-  private static final String BUCKET = "BUCKET";
+  private static final String BUCKET = "testbucket";
   private static final String PREFIX = "P/R/E/F/I/X";
   public static final String TEST_FILE = "test.csv";
-
-  private final AmazonS3Client s3Client = 
EasyMock.createMock(AmazonS3Client.class);
-  private final ServerSideEncryptingAmazonS3 service = new 
ServerSideEncryptingAmazonS3(
-      s3Client,
-      new NoopServerSideEncryption()
-  );
-  private final ListObjectsV2Result testResult = 
EasyMock.createMock(ListObjectsV2Result.class);
-
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+  @Container
+  private static final MinIOContainer MINIO = MinioUtil.createContainer();
+  @TempDir
+  public static File temporaryFolder;
+  private ServerSideEncryptingAmazonS3 s3Client;
 
   private StorageConnector storageConnector;
 
-  @Before
-  public void setup()
+  @BeforeEach
+  public void setup() throws IOException
   {
-    try {
-      S3OutputConfig s3OutputConfig = new S3OutputConfig(
-          BUCKET,
-          PREFIX,
-          temporaryFolder.newFolder(),
-          null,
-          null,
-          true
-      );
-      storageConnector = new S3StorageConnector(s3OutputConfig, service, new 
S3UploadManager(
-          s3OutputConfig,
-          new S3ExportConfig("tempDir", new HumanReadableBytes("5MiB"), 1, 
null),
-          new DruidProcessingConfigTest.MockRuntimeInfo(10, 0, 0),
-          new StubServiceEmitter()));
-    }
-    catch (IOException e) {
-      throw new RuntimeException(e);
+    s3Client = MinioUtil.createS3Client(MINIO);
+    if (!s3Client.getAmazonS3().doesBucketExistV2(BUCKET)) {
+      s3Client.getAmazonS3().createBucket(new CreateBucketRequest(BUCKET));
     }
+
+    S3OutputConfig s3OutputConfig = new S3OutputConfig(
+        BUCKET,
+        PREFIX,
+        Files.createDirectory(Paths.get(temporaryFolder.getAbsolutePath(), 
UUID.randomUUID().toString())).toFile(),
+        null,
+        null,
+        true
+    );
+    storageConnector = new S3StorageConnector(
+        s3OutputConfig,
+        s3Client,
+        new S3UploadManager(
+            s3OutputConfig,
+            new S3ExportConfig("tempDir", new HumanReadableBytes("5MiB"), 1, 
null),
+            new DruidProcessingConfigTest.MockRuntimeInfo(10, 0, 0),
+            new StubServiceEmitter()
+        )
+    );
   }
 
   @Test
   public void pathExists_yes() throws IOException
   {
-    final Capture<GetObjectMetadataRequest> request = Capture.newInstance();
-    EasyMock.reset(s3Client);
-    EasyMock.expect(s3Client.getObjectMetadata(EasyMock.capture(request)))
-            .andReturn(new ObjectMetadata());
-    EasyMock.replay(s3Client);
-    Assert.assertTrue(storageConnector.pathExists(TEST_FILE));
-    Assert.assertEquals(BUCKET, request.getValue().getBucketName());
-    Assert.assertEquals(PREFIX + "/" + TEST_FILE, request.getValue().getKey());
-    EasyMock.verify(s3Client);
+    s3Client.putObject(
+        BUCKET,
+        JOINER.join(PREFIX, TEST_FILE),
+        Files.createFile(Path.of(temporaryFolder.toPath().toString(), 
TEST_FILE)).toFile()
+    );
+    Assertions.assertTrue(storageConnector.pathExists(TEST_FILE));
   }
 
   @Test
   public void pathExists_notFound() throws IOException
   {
-    final Capture<GetObjectMetadataRequest> request = Capture.newInstance();
-    final AmazonS3Exception e = new AmazonS3Exception("not found");
-    e.setStatusCode(404);
-
-    EasyMock.reset(s3Client);
-    EasyMock.expect(s3Client.getObjectMetadata(EasyMock.capture(request)))
-            .andThrow(e);
-    EasyMock.replay(s3Client);
-    Assert.assertFalse(storageConnector.pathExists(TEST_FILE));
-    Assert.assertEquals(BUCKET, request.getValue().getBucketName());
-    Assert.assertEquals(PREFIX + "/" + TEST_FILE, request.getValue().getKey());
-    EasyMock.verify(s3Client);
+    
Assertions.assertFalse(storageConnector.pathExists(UUID.randomUUID().toString()));
   }
 
   @Test
-  public void pathExists_error()
+  public void pathExists_error() throws IOException
   {
-    final Capture<GetObjectMetadataRequest> request = Capture.newInstance();
-    final AmazonS3Exception e = new AmazonS3Exception("not found");
-    e.setStatusCode(403);
-
-    EasyMock.reset(s3Client);
-    EasyMock.expect(s3Client.getObjectMetadata(EasyMock.capture(request)))
-            .andThrow(e);
-    EasyMock.replay(s3Client);
-    final IOException e2 = Assert.assertThrows(
+    S3OutputConfig s3OutputConfig = new S3OutputConfig(
+        BUCKET,
+        PREFIX,
+        Files.createDirectory(Paths.get(temporaryFolder.getAbsolutePath(), 
UUID.randomUUID().toString())).toFile(),
+        null,
+        null,
+        true
+    );
+    StorageConnector unauthorizedStorageConnector = new S3StorageConnector(
+        s3OutputConfig,
+        MinioUtil.createUnauthorizedS3Client(MINIO),
+        new S3UploadManager(
+            s3OutputConfig,
+            new S3ExportConfig("tempDir", new HumanReadableBytes("5MiB"), 1, 
null),
+            new DruidProcessingConfigTest.MockRuntimeInfo(10, 0, 0),
+            new StubServiceEmitter()
+        )
+    );
+    final IOException e2 = Assertions.assertThrows(
         IOException.class,
-        () -> storageConnector.pathExists(TEST_FILE)
+        () -> unauthorizedStorageConnector.pathExists(TEST_FILE)
     );
-    Assert.assertEquals(BUCKET, request.getValue().getBucketName());
-    Assert.assertEquals(PREFIX + "/" + TEST_FILE, request.getValue().getKey());
-    MatcherAssert.assertThat(e2, 
ThrowableCauseMatcher.hasCause(CoreMatchers.instanceOf(AmazonS3Exception.class)));
-    EasyMock.verify(s3Client);
+    Assertions.assertEquals(AmazonS3Exception.class, e2.getCause().getClass());
+    AmazonS3Exception amazonS3Exception = (AmazonS3Exception) e2.getCause();
+    Assertions.assertEquals(403, amazonS3Exception.getStatusCode());
   }
 
   @Test
   public void pathRead() throws IOException
   {
-    EasyMock.reset(s3Client);
-    ObjectMetadata objectMetadata = new ObjectMetadata();
-    long contentLength = "test".getBytes(StandardCharsets.UTF_8).length;
-    objectMetadata.setContentLength(contentLength);
-    S3Object s3Object = new S3Object();
-    s3Object.setObjectContent(new 
ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)));
-    
EasyMock.expect(s3Client.getObjectMetadata(EasyMock.anyObject())).andReturn(objectMetadata);
-    EasyMock.expect(s3Client.getObject(
-        new GetObjectRequest(BUCKET, PREFIX + "/" + TEST_FILE).withRange(0, 
contentLength - 1))
-    ).andReturn(s3Object);
-    EasyMock.replay(s3Client);
-
-    String readText = new BufferedReader(
-        new InputStreamReader(storageConnector.read(TEST_FILE), 
StandardCharsets.UTF_8))
-        .lines()
-        .collect(Collectors.joining("\n"));
-
-    Assert.assertEquals("test", readText);
-    EasyMock.reset(s3Client);
+    try (OutputStream outputStream = storageConnector.write("readWrite1")) {
+      outputStream.write("test".getBytes(StandardCharsets.UTF_8));
+    }
+    try (InputStream inputStream = storageConnector.read("readWrite1")) {
+      byte[] bytes = inputStream.readAllBytes();
+      Assertions.assertEquals("test", new String(bytes, 
StandardCharsets.UTF_8));
+    }
   }
 
   @Test
@@ -180,139 +167,175 @@ public class S3StorageConnectorTest
   {
     String data = "test";
 
+    try (OutputStream outputStream = storageConnector.write("readWrite2")) {
+      outputStream.write(data.getBytes(StandardCharsets.UTF_8));
+    }
+
     // non empty reads
     for (int start = 0; start < data.length(); start++) {
       for (int length = 1; length <= data.length() - start; length++) {
         String dataQueried = data.substring(start, start + length);
-        EasyMock.reset(s3Client);
-        S3Object s3Object = new S3Object();
-        s3Object.setObjectContent(
-            new 
ByteArrayInputStream(dataQueried.getBytes(StandardCharsets.UTF_8))
-        );
-        EasyMock.expect(
-            s3Client.getObject(
-                new GetObjectRequest(BUCKET, PREFIX + "/" + 
TEST_FILE).withRange(start, start + length - 1)
-            )
-        ).andReturn(s3Object);
-        EasyMock.replay(s3Client);
-
-        InputStream is = storageConnector.readRange(TEST_FILE, start, length);
-        byte[] dataBytes = new byte[length];
-        Assert.assertEquals(length, is.read(dataBytes));
-        Assert.assertEquals(-1, is.read()); // reading further produces no data
-        Assert.assertEquals(dataQueried, new String(dataBytes, 
StandardCharsets.UTF_8));
-        EasyMock.reset(s3Client);
+        try (InputStream inputStream = 
storageConnector.readRange("readWrite2", start, length)) {
+          byte[] bytes = inputStream.readAllBytes();
+          Assertions.assertEquals(dataQueried, new String(bytes, 
StandardCharsets.UTF_8));
+        }
       }
     }
 
     // empty read
-    EasyMock.reset(s3Client);
-    S3Object s3Object = new S3Object();
-    s3Object.setObjectContent(
-        new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))
-    );
-    EasyMock.expect(
-        s3Client.getObject(
-            new GetObjectRequest(BUCKET, PREFIX + "/" + 
TEST_FILE).withRange(0, -1)
-        )
-    ).andReturn(s3Object);
-    EasyMock.replay(s3Client);
-
-    InputStream is = storageConnector.readRange(TEST_FILE, 0, 0);
-    byte[] dataBytes = new byte[0];
-    Assert.assertEquals(is.read(dataBytes), -1);
-    Assert.assertEquals("", new String(dataBytes, StandardCharsets.UTF_8));
-    EasyMock.reset(s3Client);
+    try (InputStream inputStream = storageConnector.readRange("readWrite2", 0, 
0)) {
+      byte[] bytes = inputStream.readAllBytes();
+      Assertions.assertEquals("", new String(bytes, StandardCharsets.UTF_8));
+    }
   }
 
   @Test
   public void testDeleteSinglePath() throws IOException
   {
-    EasyMock.reset(s3Client);
-    s3Client.deleteObject(BUCKET, PREFIX + "/" + TEST_FILE);
-    EasyMock.expectLastCall();
-    storageConnector.deleteFile(TEST_FILE);
-    EasyMock.reset(s3Client);
+    String deleteFolderName = UUID.randomUUID().toString();
+    try (OutputStream outputStream = 
storageConnector.write(StringUtils.format("%s/deleteSingle", 
deleteFolderName))) {
+      outputStream.write("delete".getBytes(StandardCharsets.UTF_8));
+    }
+
+    ArrayList<String> listResult = new ArrayList<>();
+    storageConnector.listDir(deleteFolderName + 
"/").forEachRemaining(listResult::add);
+    Assertions.assertEquals(1, listResult.size());
+    Assertions.assertEquals("deleteSingle", listResult.get(0));
+    storageConnector.deleteFile(StringUtils.format("%s/deleteSingle", 
deleteFolderName));
+
+    listResult.clear();
+    storageConnector.listDir(deleteFolderName + 
"/").forEachRemaining(listResult::add);
+    Assertions.assertEquals(0, listResult.size());
   }
 
   @Test
   public void testDeleteMultiplePaths() throws IOException
   {
-    EasyMock.reset(s3Client);
-    String testFile2 = "file2";
-    DeleteObjectsRequest deleteObjectsRequest = new 
DeleteObjectsRequest(BUCKET);
-    deleteObjectsRequest.withKeys(PREFIX + "/" + TEST_FILE, PREFIX + "/" + 
testFile2);
-    Capture<DeleteObjectsRequest> capturedArgument = EasyMock.newCapture();
-
-    
EasyMock.expect(s3Client.deleteObjects(EasyMock.capture(capturedArgument))).andReturn(null).once();
-    EasyMock.replay(s3Client);
-    storageConnector.deleteFiles(Lists.newArrayList(TEST_FILE, testFile2));
-
-    Assert.assertEquals(
-        convertDeleteObjectsRequestToString(deleteObjectsRequest),
-        convertDeleteObjectsRequestToString(capturedArgument.getValue())
-    );
-    EasyMock.reset(s3Client);
+    String deleteFolderName = UUID.randomUUID().toString();
+    try (OutputStream outputStream = 
storageConnector.write(StringUtils.format("%s/deleteFirst", deleteFolderName))) 
{
+      outputStream.write("first".getBytes(StandardCharsets.UTF_8));
+    }
+    try (OutputStream outputStream = 
storageConnector.write(StringUtils.format("%s/deleteSecond", 
deleteFolderName))) {
+      outputStream.write("second".getBytes(StandardCharsets.UTF_8));
+    }
+
+    ArrayList<String> listResult = new ArrayList<>();
+    storageConnector.listDir(deleteFolderName + 
"/").forEachRemaining(listResult::add);
+    Assertions.assertEquals(2, listResult.size());
+    Assertions.assertEquals("deleteFirst", listResult.get(0));
+    Assertions.assertEquals("deleteSecond", listResult.get(1));
+
+    storageConnector.deleteFiles(ImmutableList.of(
+        StringUtils.format("%s/deleteFirst", deleteFolderName),
+        StringUtils.format("%s/deleteSecond", deleteFolderName)
+    ));
+
+    listResult.clear();
+    storageConnector.listDir(deleteFolderName + 
"/").forEachRemaining(listResult::add);
+    Assertions.assertEquals(0, listResult.size());
   }
 
   @Test
   public void testPathDeleteRecursively() throws IOException
   {
-    EasyMock.reset(s3Client, testResult);
-
-    S3ObjectSummary s3ObjectSummary = new S3ObjectSummary();
-    s3ObjectSummary.setBucketName(BUCKET);
-    s3ObjectSummary.setKey(PREFIX + "/test/" + TEST_FILE);
-    s3ObjectSummary.setSize(1);
-    EasyMock.expect(s3Client.listObjectsV2((ListObjectsV2Request) 
EasyMock.anyObject()))
-            .andReturn(testResult);
-
-    EasyMock.expect(testResult.getBucketName()).andReturn("123").anyTimes();
-    
EasyMock.expect(testResult.getObjectSummaries()).andReturn(Collections.singletonList(s3ObjectSummary)).anyTimes();
-    EasyMock.expect(testResult.isTruncated()).andReturn(false).times(1);
-    EasyMock.expect(testResult.getNextContinuationToken()).andReturn(null);
-
-    Capture<DeleteObjectsRequest> capturedArgument = EasyMock.newCapture();
-    EasyMock.expect(s3Client.deleteObjects(EasyMock.and(
-        EasyMock.capture(capturedArgument),
-        EasyMock.isA(DeleteObjectsRequest.class)
-    ))).andReturn(null);
-    EasyMock.replay(s3Client, testResult);
-
-    storageConnector.deleteRecursively("test");
-
-    Assert.assertEquals(1, capturedArgument.getValue().getKeys().size());
-    Assert.assertEquals(PREFIX + "/test/" + TEST_FILE, 
capturedArgument.getValue().getKeys().get(0).getKey());
-    EasyMock.reset(s3Client, testResult);
+    String deleteFolderName = UUID.randomUUID().toString();
+    try (OutputStream outputStream = 
storageConnector.write(StringUtils.format("%s/deleteFirst", deleteFolderName))) 
{
+      outputStream.write("first".getBytes(StandardCharsets.UTF_8));
+    }
+    try (OutputStream outputStream = 
storageConnector.write(StringUtils.format("%s/inner/deleteSecond", 
deleteFolderName))) {
+      outputStream.write("second".getBytes(StandardCharsets.UTF_8));
+    }
+
+    ArrayList<String> listResult = new ArrayList<>();
+    storageConnector.listDir(deleteFolderName + 
"/").forEachRemaining(listResult::add);
+    Assertions.assertEquals(2, listResult.size());
+    Assertions.assertEquals("deleteFirst", listResult.get(0));
+    Assertions.assertEquals("inner/deleteSecond", listResult.get(1));
+
+    storageConnector.deleteRecursively(deleteFolderName);
+
+    listResult.clear();
+    storageConnector.listDir(deleteFolderName + 
"/").forEachRemaining(listResult::add);
+    Assertions.assertEquals(0, listResult.size());
   }
 
   @Test
   public void testListDir() throws IOException
   {
-    EasyMock.reset(s3Client, testResult);
-
-    S3ObjectSummary s3ObjectSummary = new S3ObjectSummary();
-    s3ObjectSummary.setBucketName(BUCKET);
-    s3ObjectSummary.setKey(PREFIX + "/test/" + TEST_FILE);
-    s3ObjectSummary.setSize(1);
-
-    
EasyMock.expect(testResult.getObjectSummaries()).andReturn(Collections.singletonList(s3ObjectSummary)).times(2);
-    EasyMock.expect(testResult.isTruncated()).andReturn(false);
-    EasyMock.expect(testResult.getNextContinuationToken()).andReturn(null);
-    EasyMock.expect(s3Client.listObjectsV2((ListObjectsV2Request) 
EasyMock.anyObject()))
-            .andReturn(testResult);
-    EasyMock.replay(s3Client, testResult);
-
-    List<String> listDirResult = 
Lists.newArrayList(storageConnector.listDir("test/"));
-    Assert.assertEquals(ImmutableList.of(TEST_FILE), listDirResult);
+    String listFolderName = UUID.randomUUID().toString();
+    try (OutputStream outputStream = 
storageConnector.write(StringUtils.format("%s/listFirst", listFolderName))) {
+      outputStream.write("first".getBytes(StandardCharsets.UTF_8));
+    }
+    List<String> listDirResult = 
Lists.newArrayList(storageConnector.listDir(listFolderName + "/"));
+    Assertions.assertEquals(ImmutableList.of("listFirst"), listDirResult);
   }
 
-  private String convertDeleteObjectsRequestToString(DeleteObjectsRequest 
deleteObjectsRequest)
+  // adapted from apache iceberg tests
+  private static class MinioUtil
   {
-    return deleteObjectsRequest.getKeys()
-                               .stream()
-                               .map(keyVersion -> keyVersion.getKey() + 
keyVersion.getVersion())
-                               .collect(
-                                   Collectors.joining());
+    private MinioUtil()
+    {
+    }
+
+    public static MinIOContainer createContainer()
+    {
+      return createContainer(null);
+    }
+
+    public static MinIOContainer createContainer(AWSCredentials credentials)
+    {
+      MinIOContainer container = new 
MinIOContainer(DockerImageName.parse("minio/minio:latest"));
+
+      // this enables virtual-host-style requests. see
+      // https://github.com/minio/minio/tree/master/docs/config#domain
+      container.withEnv("MINIO_DOMAIN", "localhost");
+
+      if (credentials != null) {
+        container.withUserName(credentials.getAWSAccessKeyId());
+        container.withPassword(credentials.getAWSSecretKey());
+      }
+
+      return container;
+    }
+
+    public static ServerSideEncryptingAmazonS3 createS3Client(MinIOContainer 
container)
+    {
+      final AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3Client
+          .builder()
+          .withEndpointConfiguration(
+              new AwsClientBuilder.EndpointConfiguration(
+                  container.getS3URL(),
+                  "us-east-1"
+              )
+          )
+          .withCredentials(new StaticCredentialsProvider(
+              new BasicAWSCredentials(container.getUserName(), 
container.getPassword())))
+          .withClientConfiguration(new 
ClientConfigurationFactory().getConfig())
+          .withPathStyleAccessEnabled(true); // OSX won't resolve subdomains
+
+      return ServerSideEncryptingAmazonS3.builder()
+                                         
.setAmazonS3ClientBuilder(amazonS3ClientBuilder)
+                                         .build();
+    }
+
+    public static ServerSideEncryptingAmazonS3 
createUnauthorizedS3Client(MinIOContainer container)
+    {
+      final AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3Client
+          .builder()
+          .withEndpointConfiguration(
+              new AwsClientBuilder.EndpointConfiguration(
+                  container.getS3URL(),
+                  "us-east-1"
+              )
+          )
+          .withCredentials(new StaticCredentialsProvider(
+              new BasicAWSCredentials(container.getUserName(), "wrong")))
+          .withClientConfiguration(new 
ClientConfigurationFactory().getConfig())
+          .withPathStyleAccessEnabled(true); // OSX won't resolve subdomains
+
+      return ServerSideEncryptingAmazonS3.builder()
+                                         
.setAmazonS3ClientBuilder(amazonS3ClientBuilder)
+                                         .build();
+    }
   }
 }
diff --git a/pom.xml b/pom.xml
index ac9b91d23d0..3ee7600d543 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1403,6 +1403,18 @@
               <artifactId>joni</artifactId>
               <version>2.1.34</version>
           </dependency>
+            <dependency>
+                <groupId>org.testcontainers</groupId>
+                <artifactId>testcontainers</artifactId>
+                <version>1.16.3</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.testcontainers</groupId>
+                <artifactId>junit-jupiter</artifactId>
+                <version>1.16.3</version>
+                <scope>test</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -2257,5 +2269,8 @@
             <jacoco.skip>true</jacoco.skip>
           </properties>
         </profile>
+        <profile>
+            <id>ci</id>
+        </profile>
     </profiles>
 </project>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to