From 976d9791572117dc703d8d7d6963bdd6603ecd63 Mon Sep 17 00:00:00 2001 From: Ajit Thakor <49403056+athakor@users.noreply.github.com> Date: Fri, 5 Jun 2020 00:54:28 +0530 Subject: [PATCH] feat: add support to customize gcloud command of LocalDatastoreHelper (#137) Refactor creation of LocalDatastoreHelper to provide a builder (in addition to the existing factory methods) which allow finer grained configuration including the ability to set a storage directory to be passed when starting the emulator. --- .../testing/LocalDatastoreHelper.java | 105 ++++++++++++++---- .../testing/ITLocalDatastoreHelperTest.java | 93 ++++++++++++++++ 2 files changed, 176 insertions(+), 22 deletions(-) diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/LocalDatastoreHelper.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/LocalDatastoreHelper.java index 51bcb6d32..2867d7e30 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/LocalDatastoreHelper.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/testing/LocalDatastoreHelper.java @@ -35,7 +35,6 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.TimeoutException; -import java.util.logging.Level; import java.util.logging.Logger; import org.threeten.bp.Duration; @@ -50,6 +49,7 @@ public class LocalDatastoreHelper extends BaseEmulatorHelper { private final List emulatorRunners; private final double consistency; private final Path gcdPath; + private boolean storeOnDisk; // Gcloud emulator settings private static final String GCLOUD_CMD_TEXT = "gcloud beta emulators datastore start"; @@ -78,39 +78,87 @@ public class LocalDatastoreHelper extends BaseEmulatorHelper { } } - private LocalDatastoreHelper(double consistency, int port) { + /** A builder for {@code LocalDatastoreHelper} objects. */ + public static class Builder { + private double consistency; + private int port; + private Path dataDir; + private boolean storeOnDisk = true; + + private Builder() {} + + private Builder(LocalDatastoreHelper helper) { + this.consistency = helper.consistency; + this.dataDir = helper.gcdPath; + this.storeOnDisk = helper.storeOnDisk; + } + + public Builder setConsistency(double consistency) { + this.consistency = consistency; + return this; + } + + public Builder setPort(int port) { + this.port = port; + return this; + } + + public Builder setDataDir(Path dataDir) { + this.dataDir = dataDir; + return this; + } + + public Builder setStoreOnDisk(boolean storeOnDisk) { + this.storeOnDisk = storeOnDisk; + return this; + } + + /** Creates a {@code LocalDatastoreHelper} object. */ + public LocalDatastoreHelper build() { + return new LocalDatastoreHelper(this); + } + } + + private LocalDatastoreHelper(Builder builder) { super( "datastore", - port > 0 ? port : BaseEmulatorHelper.findAvailablePort(DEFAULT_PORT), + builder.port > 0 ? builder.port : BaseEmulatorHelper.findAvailablePort(DEFAULT_PORT), PROJECT_ID_PREFIX + UUID.randomUUID().toString()); - Path tmpDirectory = null; - try { - tmpDirectory = Files.createTempDirectory("gcd"); - } catch (IOException ex) { - getLogger().log(Level.WARNING, "Failed to create temporary directory"); - } - this.gcdPath = tmpDirectory; - this.consistency = consistency; + this.consistency = builder.consistency > 0 ? builder.consistency : DEFAULT_CONSISTENCY; + this.gcdPath = builder.dataDir; + this.storeOnDisk = builder.storeOnDisk; String binName = BIN_NAME; if (isWindows()) { binName = BIN_NAME.replace("/", "\\"); } List gcloudCommand = new ArrayList<>(Arrays.asList(GCLOUD_CMD_TEXT.split(" "))); gcloudCommand.add(GCLOUD_CMD_PORT_FLAG + "localhost:" + getPort()); - gcloudCommand.add(CONSISTENCY_FLAG + consistency); - gcloudCommand.add("--no-store-on-disk"); + gcloudCommand.add(CONSISTENCY_FLAG + builder.consistency); + if (!builder.storeOnDisk) { + gcloudCommand.add("--no-store-on-disk"); + } GcloudEmulatorRunner gcloudRunner = new GcloudEmulatorRunner(gcloudCommand, VERSION_PREFIX, MIN_VERSION); List binCommand = new ArrayList<>(Arrays.asList(binName, "start")); binCommand.add("--testing"); binCommand.add(BIN_CMD_PORT_FLAG + getPort()); - binCommand.add(CONSISTENCY_FLAG + consistency); - if (gcdPath != null) { - gcloudCommand.add("--data-dir=" + gcdPath.toString()); + binCommand.add(CONSISTENCY_FLAG + getConsistency()); + if (builder.dataDir != null) { + gcloudCommand.add("--data-dir=" + getGcdPath()); } DownloadableEmulatorRunner downloadRunner = new DownloadableEmulatorRunner(binCommand, EMULATOR_URL, MD5_CHECKSUM); - emulatorRunners = ImmutableList.of(gcloudRunner, downloadRunner); + this.emulatorRunners = ImmutableList.of(gcloudRunner, downloadRunner); + } + + /** Returns a builder for {@code LocalDatastoreHelper} object. */ + public LocalDatastoreHelper.Builder toBuilder() { + return new Builder(this); + } + + /** Returns a builder for {@code LocalDatastoreHelper} object. */ + public static LocalDatastoreHelper.Builder newBuilder() { + return new LocalDatastoreHelper.Builder(); } @Override @@ -153,6 +201,16 @@ public double getConsistency() { return consistency; } + /** Returns the data directory path of the local Datastore emulator. */ + public Path getGcdPath() { + return gcdPath; + } + + /** Returns {@code true} data persist on disk, otherwise {@code false} data not store on disk. */ + public boolean isStoreOnDisk() { + return storeOnDisk; + } + /** * Creates a local Datastore helper with the specified settings for project ID and consistency. * @@ -162,7 +220,7 @@ public double getConsistency() { * consistency of non-ancestor queries; non-ancestor queries are eventually consistent. */ public static LocalDatastoreHelper create(double consistency) { - return create(consistency, 0); + return LocalDatastoreHelper.newBuilder().setConsistency(consistency).setPort(0).build(); } /** @@ -176,7 +234,7 @@ public static LocalDatastoreHelper create(double consistency) { * emulator will search for a free random port. */ public static LocalDatastoreHelper create(double consistency, int port) { - return new LocalDatastoreHelper(consistency, port); + return LocalDatastoreHelper.newBuilder().setConsistency(consistency).setPort(port).build(); } /** @@ -187,7 +245,10 @@ public static LocalDatastoreHelper create(double consistency, int port) { * emulator will search for a free random port. */ public static LocalDatastoreHelper create(int port) { - return new LocalDatastoreHelper(DEFAULT_CONSISTENCY, port); + return LocalDatastoreHelper.newBuilder() + .setConsistency(DEFAULT_CONSISTENCY) + .setPort(port) + .build(); } /** @@ -197,7 +258,7 @@ public static LocalDatastoreHelper create(int port) { * all writes are immediately visible. */ public static LocalDatastoreHelper create() { - return create(DEFAULT_CONSISTENCY); + return LocalDatastoreHelper.newBuilder().setConsistency(DEFAULT_CONSISTENCY).build(); } /** @@ -254,7 +315,7 @@ public void stop() throws IOException, InterruptedException, TimeoutException { stop(Duration.ofSeconds(20)); } - private static void deleteRecursively(Path path) throws IOException { + static void deleteRecursively(Path path) throws IOException { if (path == null || !Files.exists(path)) { return; } diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/testing/ITLocalDatastoreHelperTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/testing/ITLocalDatastoreHelperTest.java index 9da207a4a..6cc236a8b 100644 --- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/testing/ITLocalDatastoreHelperTest.java +++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/testing/ITLocalDatastoreHelperTest.java @@ -30,8 +30,12 @@ import com.google.cloud.datastore.Entity; import com.google.cloud.datastore.Key; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.concurrent.TimeoutException; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -43,6 +47,17 @@ public class ITLocalDatastoreHelperTest { private static final double TOLERANCE = 0.00001; private static final String PROJECT_ID_PREFIX = "test-project-"; private static final String NAMESPACE = "namespace"; + private Path dataDir; + + @Before + public void setUp() throws IOException { + dataDir = Files.createTempDirectory("gcd"); + } + + @After + public void tearDown() throws IOException { + LocalDatastoreHelper.deleteRecursively(dataDir); + } @Test public void testCreate() { @@ -54,6 +69,57 @@ public void testCreate() { assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX)); } + @Test + public void testCreateWithBuilder() { + LocalDatastoreHelper helper = + LocalDatastoreHelper.newBuilder() + .setConsistency(0.75) + .setPort(8081) + .setStoreOnDisk(false) + .setDataDir(dataDir) + .build(); + assertTrue(Math.abs(0.75 - helper.getConsistency()) < TOLERANCE); + assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX)); + assertFalse(helper.isStoreOnDisk()); + assertEquals(8081, helper.getPort()); + assertEquals(dataDir, helper.getGcdPath()); + LocalDatastoreHelper incompleteHelper = LocalDatastoreHelper.newBuilder().build(); + assertTrue(Math.abs(0.9 - incompleteHelper.getConsistency()) < TOLERANCE); + assertTrue(incompleteHelper.getProjectId().startsWith(PROJECT_ID_PREFIX)); + } + + @Test + public void testCreateWithToBuilder() throws IOException { + LocalDatastoreHelper helper = + LocalDatastoreHelper.newBuilder() + .setConsistency(0.75) + .setPort(8081) + .setStoreOnDisk(false) + .setDataDir(dataDir) + .build(); + assertTrue(Math.abs(0.75 - helper.getConsistency()) < TOLERANCE); + assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX)); + assertFalse(helper.isStoreOnDisk()); + assertEquals(8081, helper.getPort()); + assertEquals(dataDir, helper.getGcdPath()); + LocalDatastoreHelper actualHelper = helper.toBuilder().build(); + assertLocalDatastoreHelpersEquivelent(helper, actualHelper); + Path dataDir = Files.createTempDirectory("gcd_data_dir"); + actualHelper = + helper + .toBuilder() + .setConsistency(0.85) + .setPort(9091) + .setStoreOnDisk(true) + .setDataDir(dataDir) + .build(); + assertTrue(Math.abs(0.85 - actualHelper.getConsistency()) < TOLERANCE); + assertTrue(actualHelper.isStoreOnDisk()); + assertEquals(9091, actualHelper.getPort()); + assertEquals(dataDir, actualHelper.getGcdPath()); + LocalDatastoreHelper.deleteRecursively(dataDir); + } + @Test public void testCreatePort() { LocalDatastoreHelper helper = LocalDatastoreHelper.create(0.75, 8888); @@ -103,4 +169,31 @@ public void testStartStopReset() throws IOException, InterruptedException, Timeo assertNotNull(ex.getMessage()); } } + + @Test + public void testStartStopResetWithBuilder() + throws IOException, InterruptedException, TimeoutException { + try { + LocalDatastoreHelper helper = LocalDatastoreHelper.newBuilder().build(); + helper.start(); + Datastore datastore = helper.getOptions().getService(); + Key key = datastore.newKeyFactory().setKind("kind").newKey("name"); + datastore.put(Entity.newBuilder(key).build()); + assertNotNull(datastore.get(key)); + helper.reset(); + assertNull(datastore.get(key)); + helper.stop(Duration.ofMinutes(1)); + datastore.get(key); + Assert.fail(); + } catch (DatastoreException ex) { + assertNotNull(ex.getMessage()); + } + } + + public void assertLocalDatastoreHelpersEquivelent( + LocalDatastoreHelper expected, LocalDatastoreHelper actual) { + assertEquals(expected.getConsistency(), actual.getConsistency(), 0); + assertEquals(expected.isStoreOnDisk(), actual.isStoreOnDisk()); + assertEquals(expected.getGcdPath(), actual.getGcdPath()); + } }