Use CouchbaseMock instead of actual server
Seems way more reliable/consistent for test scenarios.
This commit is contained in:
parent
0655ef208f
commit
444cbecba5
|
@ -64,8 +64,6 @@ jobs:
|
|||
- image: *default_container
|
||||
# This is used by spymemcached instrumentation tests
|
||||
- image: memcached
|
||||
# This is used by couchbase instrumentation tests
|
||||
- image: couchbase/server
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
|
|
@ -32,13 +32,13 @@ dependencies {
|
|||
annotationProcessor deps.autoservice
|
||||
implementation deps.autoservice
|
||||
|
||||
testCompile 'com.anotherchrisberry:spock-retry:0.6.4'
|
||||
|
||||
testCompile project(':dd-java-agent:testing')
|
||||
|
||||
testCompile group: 'org.testcontainers', name: 'couchbase', version: '1.8.3'
|
||||
testCompile group: 'com.couchbase.mock', name: 'CouchbaseMock', version: '1.5.19'
|
||||
|
||||
testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '2.0.0.RELEASE'
|
||||
// Earliest version that seems to allow queries with CouchbaseMock:
|
||||
testCompile group: 'com.couchbase.client', name: 'java-client', version: '2.5.0'
|
||||
|
||||
latestDepTestCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '3.+'
|
||||
latestDepTestCompile group: 'com.couchbase.client', name: 'java-client', version: '2.6+'
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import com.couchbase.client.java.Bucket
|
||||
import com.couchbase.client.java.document.JsonDocument
|
||||
import com.couchbase.client.java.document.json.JsonObject
|
||||
import com.couchbase.client.java.query.N1qlParams
|
||||
import com.couchbase.client.java.query.N1qlQuery
|
||||
import com.couchbase.client.java.query.consistency.ScanConsistency
|
||||
import util.AbstractCouchbaseTest
|
||||
|
||||
import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces
|
||||
|
@ -33,7 +31,7 @@ class CouchbaseClientTest extends AbstractCouchbaseTest {
|
|||
|
||||
when:
|
||||
// Connect to the bucket and open it
|
||||
Bucket bkt = cluster.openBucket(bucketSettings.name())
|
||||
Bucket bkt = cluster.openBucket(bucketSettings.name(), bucketSettings.password())
|
||||
|
||||
// Create a JSON document and store it with the ID "helloworld"
|
||||
JsonObject content = JsonObject.create().put("hello", "world")
|
||||
|
@ -84,52 +82,26 @@ class CouchbaseClientTest extends AbstractCouchbaseTest {
|
|||
|
||||
|
||||
where:
|
||||
bucketSettings << [bucketCouchbase, bucketMemcache, bucketEphemeral]
|
||||
manager | cluster | bucketSettings
|
||||
couchbaseManager | couchbaseCluster | bucketCouchbase
|
||||
memcacheManager | memcacheCluster | bucketMemcache
|
||||
|
||||
type = bucketSettings.type().name()
|
||||
}
|
||||
|
||||
def "test query"() {
|
||||
when:
|
||||
// Connect to the bucket and open it
|
||||
Bucket bkt = cluster.openBucket(bucketSettings.name())
|
||||
|
||||
// Create a JSON document and store it with the ID "helloworld"
|
||||
JsonObject content = JsonObject.create().put("hello", "world")
|
||||
def inserted = bkt.upsert(JsonDocument.create("helloworld", content))
|
||||
|
||||
then:
|
||||
inserted != null
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "couchbase"
|
||||
resourceName "Bucket.upsert(${bkt.name()})"
|
||||
operationName "couchbase.call"
|
||||
errored false
|
||||
parent()
|
||||
tags {
|
||||
"bucket" bkt.name()
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_WRITER.clear()
|
||||
setup:
|
||||
Bucket bkt = cluster.openBucket(bucketSettings.name(), bucketSettings.password())
|
||||
|
||||
when:
|
||||
def result = bkt.query(
|
||||
N1qlQuery.parameterized(
|
||||
"SELECT * FROM `${bkt.name()}` WHERE hello = \$hello",
|
||||
JsonObject.create()
|
||||
.put("hello", "world"),
|
||||
N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)
|
||||
)
|
||||
)
|
||||
// Mock expects this specific query.
|
||||
// See com.couchbase.mock.http.query.QueryServer.handleString.
|
||||
def result = bkt.query(N1qlQuery.simple("SELECT mockrow"))
|
||||
|
||||
then:
|
||||
result.parseSuccess()
|
||||
result.finalSuccess()
|
||||
result.first().value().get(bkt.name()).get("hello") == "world"
|
||||
result.first().value().get("row") == "value"
|
||||
|
||||
and:
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
|
@ -149,7 +121,10 @@ class CouchbaseClientTest extends AbstractCouchbaseTest {
|
|||
}
|
||||
|
||||
where:
|
||||
bucketSettings << [bucketCouchbase] // Only couchbase buckets support queries.
|
||||
manager | cluster | bucketSettings
|
||||
couchbaseManager | couchbaseCluster | bucketCouchbase
|
||||
// Only couchbase buckets support queries.
|
||||
|
||||
type = bucketSettings.type().name()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package springdata
|
||||
|
||||
|
||||
import com.couchbase.client.java.view.DefaultView
|
||||
import com.couchbase.client.java.view.DesignDocument
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.data.repository.CrudRepository
|
||||
|
@ -30,10 +31,24 @@ class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
|
|||
DocRepository repo
|
||||
|
||||
def setupSpec() {
|
||||
CouchbaseConfig.setEnvironment(environment)
|
||||
|
||||
// Create view for SpringRepository's findAll()
|
||||
couchbaseCluster.openBucket(bucketCouchbase.name(), bucketCouchbase.password()).bucketManager()
|
||||
.insertDesignDocument(
|
||||
DesignDocument.create("doc", Collections.singletonList(DefaultView.create("all",
|
||||
'''
|
||||
function (doc, meta) {
|
||||
if (doc._class == "springdata.Doc") {
|
||||
emit(meta.id, null);
|
||||
}
|
||||
}
|
||||
'''.stripIndent()
|
||||
)))
|
||||
)
|
||||
CouchbaseConfig.setEnvironment(couchbaseEnvironment)
|
||||
|
||||
// Close all buckets and disconnect
|
||||
cluster.disconnect()
|
||||
couchbaseCluster.disconnect()
|
||||
|
||||
applicationContext = new AnnotationConfigApplicationContext(CouchbaseConfig)
|
||||
repo = applicationContext.getBean(DocRepository)
|
||||
|
|
|
@ -14,14 +14,11 @@ class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
|
|||
List<CouchbaseTemplate> templates
|
||||
|
||||
def setupSpec() {
|
||||
Bucket bucketCouchbase = cluster.openBucket(bucketCouchbase.name())
|
||||
Bucket bucketMemcache = cluster.openBucket(bucketMemcache.name())
|
||||
Bucket bucketEphemeral = cluster.openBucket(bucketEphemeral.name())
|
||||
def info = manager.info()
|
||||
Bucket bucketCouchbase = couchbaseCluster.openBucket(bucketCouchbase.name(), bucketCouchbase.password())
|
||||
Bucket bucketMemcache = memcacheCluster.openBucket(bucketMemcache.name(), bucketMemcache.password())
|
||||
|
||||
templates = [new CouchbaseTemplate(info, bucketCouchbase),
|
||||
new CouchbaseTemplate(info, bucketMemcache),
|
||||
new CouchbaseTemplate(info, bucketEphemeral)]
|
||||
templates = [new CouchbaseTemplate(couchbaseManager.info(), bucketCouchbase),
|
||||
new CouchbaseTemplate(memcacheManager.info(), bucketMemcache)]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,55 +1,32 @@
|
|||
package util
|
||||
|
||||
import com.anotherchrisberry.spock.extensions.retry.RetryOnFailure
|
||||
import com.couchbase.client.core.env.AbstractServiceConfig
|
||||
import com.couchbase.client.core.env.KeyValueServiceConfig
|
||||
import com.couchbase.client.core.env.QueryServiceConfig
|
||||
import com.couchbase.client.core.env.SearchServiceConfig
|
||||
import com.couchbase.client.core.env.ViewServiceConfig
|
||||
|
||||
import com.couchbase.client.core.metrics.DefaultLatencyMetricsCollectorConfig
|
||||
import com.couchbase.client.core.metrics.DefaultMetricsCollectorConfig
|
||||
import com.couchbase.client.core.utils.Base64
|
||||
import com.couchbase.client.java.Bucket
|
||||
import com.couchbase.client.java.CouchbaseCluster
|
||||
import com.couchbase.client.java.bucket.BucketType
|
||||
import com.couchbase.client.java.cluster.AuthDomain
|
||||
import com.couchbase.client.java.cluster.BucketSettings
|
||||
import com.couchbase.client.java.cluster.ClusterManager
|
||||
import com.couchbase.client.java.cluster.DefaultBucketSettings
|
||||
import com.couchbase.client.java.cluster.UserRole
|
||||
import com.couchbase.client.java.cluster.UserSettings
|
||||
import com.couchbase.client.java.env.CouchbaseEnvironment
|
||||
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment
|
||||
import com.couchbase.client.java.query.Index
|
||||
import com.couchbase.client.java.query.N1qlParams
|
||||
import com.couchbase.client.java.query.N1qlQuery
|
||||
import com.couchbase.client.java.query.consistency.ScanConsistency
|
||||
import com.couchbase.client.java.view.DefaultView
|
||||
import com.couchbase.client.java.view.DesignDocument
|
||||
import com.couchbase.mock.Bucket
|
||||
import com.couchbase.mock.BucketConfiguration
|
||||
import com.couchbase.mock.CouchbaseMock
|
||||
import com.couchbase.mock.http.query.QueryServer
|
||||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.OkHttpUtils
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.testcontainers.couchbase.CouchbaseContainer
|
||||
import spock.lang.Requires
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
// Do not run tests locally on Java7 since testcontainers are not compatible with Java7
|
||||
// It is fine to run on CI because CI provides couchbase externally, not through testcontainers
|
||||
@Requires({ "true" == System.getenv("CI") || jvm.java8Compatible })
|
||||
|
||||
// Couchbase client sometimes throws com.couchbase.client.java.error.TemporaryFailureException.
|
||||
// Lets automatically retry to avoid the test from failing completely.
|
||||
@RetryOnFailure(times = 3, delaySeconds = 1)
|
||||
abstract class AbstractCouchbaseTest extends AgentTestRunner {
|
||||
|
||||
private static final USERNAME = "Administrator"
|
||||
private static final PASSWORD = "password"
|
||||
private static final OkHttpClient HTTP_CLIENT = OkHttpUtils.client()
|
||||
|
||||
@Shared
|
||||
private int port = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
private String testBucketName = this.getClass().simpleName
|
||||
|
@ -73,177 +50,74 @@ abstract class AbstractCouchbaseTest extends AgentTestRunner {
|
|||
.build()
|
||||
|
||||
@Shared
|
||||
protected bucketEphemeral = DefaultBucketSettings.builder()
|
||||
.enableFlush(true)
|
||||
.name("$testBucketName-emp")
|
||||
.password("test-pass")
|
||||
.type(BucketType.EPHEMERAL)
|
||||
.quota(100)
|
||||
.build()
|
||||
|
||||
/*
|
||||
Note: type here has to stay undefined, otherwise tests will fail in CI in Java 7 because
|
||||
'testcontainers' are built for Java 8 and Java 7 cannot load this class.
|
||||
*/
|
||||
CouchbaseMock mock
|
||||
@Shared
|
||||
def couchbaseContainer
|
||||
protected CouchbaseCluster couchbaseCluster
|
||||
@Shared
|
||||
protected CouchbaseCluster cluster
|
||||
protected CouchbaseCluster memcacheCluster
|
||||
@Shared
|
||||
protected CouchbaseEnvironment environment
|
||||
protected CouchbaseEnvironment couchbaseEnvironment
|
||||
@Shared
|
||||
protected ClusterManager manager
|
||||
protected CouchbaseEnvironment memcacheEnvironment
|
||||
@Shared
|
||||
protected ClusterManager couchbaseManager
|
||||
@Shared
|
||||
protected ClusterManager memcacheManager
|
||||
|
||||
def setupSpec() {
|
||||
|
||||
/*
|
||||
CI will provide us with couchbase container running along side our build.
|
||||
When building locally, however, we need to take matters into our own hands
|
||||
and we use 'testcontainers' for this.
|
||||
*/
|
||||
if (false && "true" != System.getenv("CI")) {
|
||||
couchbaseContainer = new CouchbaseContainer(
|
||||
clusterUsername: USERNAME,
|
||||
clusterPassword: PASSWORD)
|
||||
couchbaseContainer.start()
|
||||
environment = couchbaseContainer.getCouchbaseEnvironment()
|
||||
cluster = couchbaseContainer.getCouchbaseCluster()
|
||||
println "Couchbase container started"
|
||||
} else {
|
||||
initCluster()
|
||||
environment = envBuilder().build()
|
||||
cluster = CouchbaseCluster.create(environment)
|
||||
println "Using provided couchbase"
|
||||
}
|
||||
manager = cluster.clusterManager(USERNAME, PASSWORD)
|
||||
mock = new CouchbaseMock("127.0.0.1", port, 1, 1)
|
||||
mock.httpServer.register("/query", new QueryServer())
|
||||
mock.start()
|
||||
println "CouchbaseMock listening on localhost:$port"
|
||||
|
||||
resetBucket(bucketCouchbase)
|
||||
resetBucket(bucketMemcache)
|
||||
resetBucket(bucketEphemeral)
|
||||
mock.createBucket(convert(bucketCouchbase))
|
||||
|
||||
couchbaseEnvironment = envBuilder(bucketCouchbase).build()
|
||||
couchbaseCluster = CouchbaseCluster.create(couchbaseEnvironment, Arrays.asList("127.0.0.1"))
|
||||
couchbaseManager = couchbaseCluster.clusterManager(USERNAME, PASSWORD)
|
||||
|
||||
mock.createBucket(convert(bucketMemcache))
|
||||
|
||||
memcacheEnvironment = envBuilder(bucketMemcache).build()
|
||||
memcacheCluster = CouchbaseCluster.create(memcacheEnvironment, Arrays.asList("127.0.0.1"))
|
||||
memcacheManager = memcacheCluster.clusterManager(USERNAME, PASSWORD)
|
||||
|
||||
// Cache buckets:
|
||||
couchbaseCluster.openBucket(bucketCouchbase.name(), bucketCouchbase.password())
|
||||
memcacheCluster.openBucket(bucketMemcache.name(), bucketMemcache.password())
|
||||
}
|
||||
|
||||
private static BucketConfiguration convert(BucketSettings bucketSettings) {
|
||||
def configuration = new BucketConfiguration()
|
||||
configuration.name = bucketSettings.name()
|
||||
configuration.password = bucketSettings.password()
|
||||
configuration.type = Bucket.BucketType.valueOf(bucketSettings.type().name())
|
||||
configuration.numNodes = 1
|
||||
configuration.numReplicas = 0
|
||||
return configuration
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
// Close all buckets and disconnect
|
||||
cluster?.disconnect()
|
||||
couchbaseCluster?.disconnect()
|
||||
memcacheCluster?.disconnect()
|
||||
|
||||
couchbaseContainer?.stop()
|
||||
mock?.stop()
|
||||
}
|
||||
|
||||
protected DefaultCouchbaseEnvironment.Builder envBuilder() {
|
||||
def timeout = TimeUnit.MINUTES.toMillis(1)
|
||||
private DefaultCouchbaseEnvironment.Builder envBuilder(BucketSettings bucketSettings) {
|
||||
def timeout = TimeUnit.SECONDS.toMillis(5)
|
||||
return DefaultCouchbaseEnvironment.builder()
|
||||
.bootstrapCarrierDirectPort(mock.getCarrierPort(bucketSettings.name()))
|
||||
.bootstrapHttpDirectPort(port)
|
||||
// settings to try to reduce variability in the tests:
|
||||
.configPollInterval(0)
|
||||
.configPollFloorInterval(0)
|
||||
.runtimeMetricsCollectorConfig(DefaultMetricsCollectorConfig.create(0, TimeUnit.DAYS))
|
||||
.networkLatencyMetricsCollectorConfig(DefaultLatencyMetricsCollectorConfig.create(0, TimeUnit.DAYS))
|
||||
.queryServiceConfig(QueryServiceConfig.create(10, 10, AbstractServiceConfig.NO_IDLE_TIME))
|
||||
.searchServiceConfig(SearchServiceConfig.create(10, 10, AbstractServiceConfig.NO_IDLE_TIME))
|
||||
.viewServiceConfig(ViewServiceConfig.create(10, 10, AbstractServiceConfig.NO_IDLE_TIME))
|
||||
.keyValueServiceConfig(KeyValueServiceConfig.create(10))
|
||||
.computationPoolSize(1)
|
||||
.analyticsTimeout(timeout)
|
||||
.connectTimeout(timeout)
|
||||
.kvTimeout(timeout)
|
||||
.managementTimeout(timeout)
|
||||
.queryTimeout(timeout)
|
||||
.searchTimeout(timeout)
|
||||
.viewTimeout(timeout)
|
||||
}
|
||||
|
||||
|
||||
protected void initCluster() {
|
||||
callCouchbaseRestAPI("/pools/default", new FormBody.Builder().add("memoryQuota", "1000").add("indexMemoryQuota", "300").build())
|
||||
callCouchbaseRestAPI("/node/controller/setupServices", new FormBody.Builder().add("services", "kv,index,n1ql,fts").build(), ["cannot change node services after cluster is provisioned"])
|
||||
callCouchbaseRestAPI("/settings/web", new FormBody.Builder().add("username", USERNAME).add("password", PASSWORD).add("port", "8091").build())
|
||||
callCouchbaseRestAPI("/settings/indexes", RequestBody.create(FormBody.CONTENT_TYPE, "indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapted from CouchbaseContainer.callCouchbaseRestAPI()
|
||||
*/
|
||||
protected void callCouchbaseRestAPI(String url, RequestBody body, expectations = null) throws IOException {
|
||||
String authToken = Base64.encode((USERNAME + ":" + PASSWORD).getBytes("UTF-8"))
|
||||
def request = new Request.Builder()
|
||||
.url("http://localhost:8091$url")
|
||||
.header("Authorization", "Basic " + authToken)
|
||||
.post(body)
|
||||
.build()
|
||||
def response = HTTP_CLIENT.newCall(request).execute()
|
||||
try {
|
||||
if (expectations != null) {
|
||||
if (expectations instanceof Integer) {
|
||||
assert response.code() == expectations
|
||||
} else if (expectations instanceof String) {
|
||||
assert response.body().string() == expectations
|
||||
}
|
||||
} else {
|
||||
assert response.code() == 200
|
||||
}
|
||||
} finally {
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from CouchbaseContainer.callCouchbaseRestAPI()
|
||||
*/
|
||||
protected void resetBucket(BucketSettings bucketSetting) {
|
||||
try {
|
||||
// Remove existing Bucket
|
||||
if (manager.hasBucket(bucketSetting.name())) {
|
||||
manager.removeBucket(bucketSetting.name())
|
||||
assert !manager.hasBucket(bucketSetting.name())
|
||||
}
|
||||
|
||||
// Calling the rest api because the library is slow and creates a lot of traces.
|
||||
callCouchbaseRestAPI("/pools/default/buckets", new FormBody.Builder()
|
||||
.add("name", bucketSetting.name())
|
||||
.add("bucketType", bucketSetting.type().name().toLowerCase())
|
||||
.add("ramQuotaMB", String.valueOf(bucketSetting.quota()))
|
||||
.add("authType", "sasl")
|
||||
.add("saslPassword", bucketSetting.password())
|
||||
.build(), 202)
|
||||
|
||||
// Insert Bucket admin user
|
||||
UserSettings userSettings = UserSettings.build().password(bucketSetting.password())
|
||||
.roles([new UserRole("bucket_full_access", bucketSetting.name())])
|
||||
manager.upsertUser(AuthDomain.LOCAL, bucketSetting.name(), userSettings)
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e)
|
||||
}
|
||||
|
||||
|
||||
Bucket bucket = cluster.openBucket(bucketSetting.name(), bucketSetting.password())
|
||||
|
||||
if (BucketType.COUCHBASE.equals(bucketSetting.type())) {
|
||||
try {
|
||||
bucket.query(N1qlQuery.simple(Index.dropPrimaryIndex(bucketSetting.name()),
|
||||
N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)))
|
||||
|
||||
assert bucket.query(N1qlQuery.simple(Index.createPrimaryIndex().on(bucketSetting.name()),
|
||||
N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS))).finalSuccess()
|
||||
|
||||
// Create view for SpringRepository's findAll()
|
||||
bucket.bucketManager().insertDesignDocument(
|
||||
DesignDocument.create("doc", Collections.singletonList(DefaultView.create("all",
|
||||
'''
|
||||
function (doc, meta) {
|
||||
if (doc._class == "springdata.Doc") {
|
||||
emit(meta.id, null);
|
||||
}
|
||||
}
|
||||
'''
|
||||
)))
|
||||
)
|
||||
TEST_WRITER.waitForTraces(13)
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e)
|
||||
}
|
||||
} else {
|
||||
TEST_WRITER.waitForTraces(4)
|
||||
}
|
||||
TEST_WRITER.clear() // remove traces generated by insertBucket
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue