diff --git a/.github/workflows/integration-tests-against-emulator.sh b/.github/workflows/integration-tests-against-emulator.sh new file mode 100644 index 00000000000..2125d5f3089 --- /dev/null +++ b/.github/workflows/integration-tests-against-emulator.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License.. + +# Fail on any error +set -e + +# Display commands being run +set -x + +export SPANNER_EMULATOR_HOST=localhost:9010 +export GOOGLE_CLOUD_PROJECT=emulator-test-project +echo "Running the Cloud Spanner emulator: $SPANNER_EMULATOR_HOST"; + +# Download the emulator +EMULATOR_VERSION=0.8.0 +wget https://storage.googleapis.com/cloud-spanner-emulator/releases/${EMULATOR_VERSION}/cloud-spanner-emulator_linux_amd64-${EMULATOR_VERSION}.tar.gz +tar zxvf cloud-spanner-emulator_linux_amd64-${EMULATOR_VERSION}.tar.gz +chmod u+x emulator_main + +# Start the emulator +./emulator_main --host_port $SPANNER_EMULATOR_HOST & + +EMULATOR_PID=$! + +# Stop the emulator & clean the environment variable +trap "kill -15 $EMULATOR_PID; unset SPANNER_EMULATOR_HOST; unset GOOGLE_CLOUD_PROJECT; echo \"Cleanup the emulator\";" EXIT + +mvn -B -Dspanner.testenv.instance="" \ + -Penable-integration-tests \ + -DtrimStackTrace=false \ + -Dclirr.skip=true \ + -Denforcer.skip=true \ + -fae \ + verify diff --git a/.github/workflows/integration-tests-against-emulator.yaml b/.github/workflows/integration-tests-against-emulator.yaml new file mode 100644 index 00000000000..ac27f8e17ea --- /dev/null +++ b/.github/workflows/integration-tests-against-emulator.yaml @@ -0,0 +1,19 @@ +on: + push: + branches: + - master + pull_request: +name: integration-tests-against-emulator +jobs: + units: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - run: java -version + - run: .kokoro/build.sh + - run: sh .github/workflows/integration-tests-against-emulator.sh + env: + JOB_TYPE: test diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java index 2ab5635431a..f20354950f0 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java @@ -129,7 +129,6 @@ public void cleanUp() { } } logger.log(Level.INFO, "Dropped {0} test database(s)", numDropped); - client.close(); } /** diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java index f643c6021ef..b67f970273c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java @@ -162,17 +162,22 @@ private void initializeInstance(InstanceId instanceId) { } private void cleanUpInstance() { - if (isOwnedInstance) { - // Delete the instance, which implicitly drops all databases in it. - try { - logger.log(Level.FINE, "Deleting test instance {0}", testHelper.getInstanceId()); - instanceAdminClient.deleteInstance(testHelper.getInstanceId().getInstance()); - logger.log(Level.INFO, "Deleted test instance {0}", testHelper.getInstanceId()); - } catch (SpannerException e) { - logger.log(Level.SEVERE, "Failed to delete test instance " + testHelper.getInstanceId(), e); + try { + if (isOwnedInstance) { + // Delete the instance, which implicitly drops all databases in it. + try { + logger.log(Level.FINE, "Deleting test instance {0}", testHelper.getInstanceId()); + instanceAdminClient.deleteInstance(testHelper.getInstanceId().getInstance()); + logger.log(Level.INFO, "Deleted test instance {0}", testHelper.getInstanceId()); + } catch (SpannerException e) { + logger.log( + Level.SEVERE, "Failed to delete test instance " + testHelper.getInstanceId(), e); + } + } else { + testHelper.cleanUp(); } - } else { - testHelper.cleanUp(); + } finally { + testHelper.getClient().close(); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java index 216ab5ca0d1..fae463ceb11 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner.connection; +import com.google.cloud.NoCredentials; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.GceTestEnvConfig; @@ -39,6 +40,7 @@ import java.util.Collections; import java.util.List; import java.util.Random; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -177,6 +179,9 @@ public static StringBuilder extractConnectionUrl(SpannerOptions options, Databas url.append(options.getHost().substring(options.getHost().indexOf(':') + 1)); } url.append("/").append(database.getId().getName()); + if (options.getCredentials() == NoCredentials.getInstance()) { + url.append(";usePlainText=true"); + } return url; } @@ -185,6 +190,11 @@ public static void setup() { database = env.getTestHelper().createTestDatabase(); } + @AfterClass + public static void teardown() { + ConnectionOptions.closeSpanner(); + } + /** * Creates a new default connection to a test database. Use the method {@link * ITAbstractSpannerTest#appendConnectionUri(StringBuilder)} to append additional connection diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITBulkConnectionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITBulkConnectionTest.java index 7a8091e0d47..dac9efc6b83 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITBulkConnectionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITBulkConnectionTest.java @@ -20,7 +20,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.IntegrationTest; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.connection.ITAbstractSpannerTest; @@ -35,8 +35,12 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Test opening multiple generic (not JDBC) Spanner connections. */ -@Category(ParallelIntegrationTest.class) +/** + * Test opening multiple generic (not JDBC) Spanner connections. This test should not be run in + * parallel with other tests, as it tries to close all active connections, and should not try to + * close connections of other integration tests. + */ +@Category(IntegrationTest.class) @RunWith(JUnit4.class) public class ITBulkConnectionTest extends ITAbstractSpannerTest { private static final int NUMBER_OF_TEST_CONNECTIONS = 250; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java index ae94e9781b2..db833492323 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java @@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.Mutation; @@ -168,6 +169,7 @@ public void testStatementTimeoutAutocommit() { @Test public void testAnalyzeQuery() { + assumeFalse("analyze query is not supported on the emulator", env.getTestHelper().isEmulator()); try (ITConnection connection = createConnection()) { for (QueryAnalyzeMode mode : QueryAnalyzeMode.values()) { try (ResultSet rs = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java index b213ed35369..5f239f2c9ec 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java @@ -19,6 +19,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeFalse; import com.google.cloud.spanner.AbortedDueToConcurrentModificationException; import com.google.cloud.spanner.Mutation; @@ -58,6 +59,10 @@ public void test01_RunScript() throws Exception { @Test public void test02_RunAbortedTest() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); + final long SINGER_ID = 2L; final long VENUE_ID = 68L; final long NUMBER_OF_SINGERS = 30L; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java index 6f343d29d50..9ff245ffe58 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java @@ -16,7 +16,9 @@ package com.google.cloud.spanner.connection.it; +import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.connection.ITAbstractSpannerTest; import com.google.cloud.spanner.connection.SqlScriptVerifier; import com.google.cloud.spanner.connection.SqlScriptVerifier.SpannerGenericConnection; @@ -73,6 +75,12 @@ public void test02_InsertTestData() throws Exception { SpannerGenericConnection.of(connection), INSERT_AND_VERIFY_TEST_DATA, SqlScriptVerifier.class); + } catch (SpannerException e) { + if (env.getTestHelper().isEmulator() && e.getErrorCode() == ErrorCode.ALREADY_EXISTS) { + // Errors in a transaction are 'sticky' on the emulator, so any query in the same + // transaction will return the same error as the error generated by a previous (update) + // statement. + } } } @@ -93,6 +101,11 @@ public void test04_TestGetCommitTimestamp() throws Exception { SpannerGenericConnection.of(connection), TEST_GET_COMMIT_TIMESTAMP, SqlScriptVerifier.class); + } catch (SpannerException e) { + if (env.getTestHelper().isEmulator() && e.getErrorCode() == ErrorCode.INVALID_ARGUMENT) { + // Errors in a transaction are 'sticky' on the emulator, so any query in the same + // transaction will return the same error as the error generated by a previous statement. + } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java index 681690e50d4..3874f595bad 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java @@ -42,7 +42,7 @@ public class ITTransactionModeTest extends ITAbstractSpannerTest { @Override public void appendConnectionUri(StringBuilder uri) { - uri.append("?autocommit=false"); + uri.append(";autocommit=false"); } @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionRetryTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionRetryTest.java index 05fb54a7916..60e85c78580 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionRetryTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionRetryTest.java @@ -19,6 +19,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeFalse; import com.google.cloud.Timestamp; import com.google.cloud.spanner.AbortedDueToConcurrentModificationException; @@ -315,7 +316,7 @@ public void testNextCallAborted() { connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test 1')")); connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (2, 'test 2')")); // do a query - try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM TEST"))) { + try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM TEST ORDER BY ID"))) { // the first record should be accessible without any problems assertThat(rs.next(), is(true)); assertThat(rs.getLong("ID"), is(equalTo(1L))); @@ -490,6 +491,9 @@ public void testAbortWithResultSetFullyConsumed() { @Test public void testAbortWithConcurrentInsert() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) { @@ -524,6 +528,9 @@ public void testAbortWithConcurrentInsert() { @Test public void testAbortWithConcurrentDelete() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records try (ITConnection connection = createConnection()) { @@ -562,6 +569,9 @@ public void testAbortWithConcurrentDelete() { @Test public void testAbortWithConcurrentUpdate() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records try (ITConnection connection = createConnection()) { @@ -605,6 +615,9 @@ public void testAbortWithConcurrentUpdate() { */ @Test public void testAbortWithUnseenConcurrentInsert() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) { @@ -652,6 +665,9 @@ public void testAbortWithUnseenConcurrentInsert() { */ @Test public void testAbortWithUnseenConcurrentInsertAbortOnNext() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); // no calls to next(), this should succeed assertThat(testAbortWithUnseenConcurrentInsertAbortOnNext(0) >= 1, is(true)); // 1 call to next() should also succeed, as there were 2 records in the original result set @@ -673,6 +689,9 @@ public void testAbortWithUnseenConcurrentInsertAbortOnNext() { private int testAbortWithUnseenConcurrentInsertAbortOnNext(int callsToNext) throws AbortedDueToConcurrentModificationException { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); int retries = 0; clearTable(); clearStatistics(); @@ -732,6 +751,9 @@ private int testAbortWithUnseenConcurrentInsertAbortOnNext(int callsToNext) */ @Test public void testAbortWithConcurrentInsertAndContinue() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) { @@ -941,6 +963,9 @@ protected boolean shouldAbort(String statement, ExecutionStep step) { */ @Test public void testNestedAbortWithConcurrentInsert() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0) { private boolean alreadyAborted = false; @@ -1003,6 +1028,9 @@ protected boolean shouldAbort(String statement, ExecutionStep step) { */ @Test public void testAbortWithDifferentUpdateCount() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records try (ITConnection connection = createConnection()) { @@ -1048,6 +1076,9 @@ public void testAbortWithDifferentUpdateCount() { */ @Test public void testAbortWithExceptionOnSelect() { + assumeFalse( + "resume after error in transaction is not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records try (ITConnection connection = createConnection()) { @@ -1097,6 +1128,9 @@ public void testAbortWithExceptionOnSelect() { */ @Test public void testAbortWithExceptionOnSelectAndConcurrentModification() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); boolean abortedDueToConcurrentModification = false; AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records @@ -1164,6 +1198,9 @@ public void testAbortWithExceptionOnSelectAndConcurrentModification() { */ @Test public void testAbortWithExceptionOnInsertAndConcurrentModification() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); boolean abortedDueToConcurrentModification = false; AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records @@ -1230,6 +1267,9 @@ public void testAbortWithExceptionOnInsertAndConcurrentModification() { */ @Test public void testAbortWithDroppedTableConcurrentModification() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); boolean abortedDueToConcurrentModification = false; AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records @@ -1292,6 +1332,9 @@ public void testAbortWithDroppedTableConcurrentModification() { */ @Test public void testAbortWithInsertOnDroppedTableConcurrentModification() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); boolean abortedDueToConcurrentModification = false; AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records @@ -1351,6 +1394,9 @@ public void testAbortWithInsertOnDroppedTableConcurrentModification() { */ @Test public void testAbortWithCursorHalfwayDroppedTableConcurrentModification() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); boolean abortedDueToConcurrentModification = false; AbortInterceptor interceptor = new AbortInterceptor(0); // first insert two test records @@ -1503,6 +1549,9 @@ public void testRetryHighAbortRate() { @Test public void testAbortWithConcurrentInsertOnEmptyTable() { + assumeFalse( + "concurrent transactions are not supported on the emulator", + env.getTestHelper().isEmulator()); AbortInterceptor interceptor = new AbortInterceptor(0); try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITCommitTimestampTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITCommitTimestampTest.java index 92d5aa3c6ed..84d1a675597 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITCommitTimestampTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITCommitTimestampTest.java @@ -34,11 +34,13 @@ import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.TimestampBound; import com.google.cloud.spanner.Value; +import com.google.cloud.spanner.connection.ConnectionOptions; import com.google.cloud.spanner.testing.RemoteSpannerHelper; import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.concurrent.ExecutionException; import org.junit.After; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -77,6 +79,11 @@ public static void setUp() { databaseId = db.getId().getDatabase(); } + @AfterClass + public static void teardown() { + ConnectionOptions.closeSpanner(); + } + @After public void deleteAllTestRecords() { client.write(ImmutableList.of(Mutation.delete("T", KeySet.all()))); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java index 1f084d5fdbf..d7a007eaf66 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java @@ -612,7 +612,7 @@ public void incorrectType() { fail("Expected exception"); } catch (SpannerException ex) { assertThat(ex.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION); - assertThat(ex.getMessage()).contains("Expected STRING"); + assertThat(ex.getMessage()).contains("STRING"); } }