Skip to content

Commit

Permalink
Created a failing test
Browse files Browse the repository at this point in the history
So it fails playback when doing things concurrently using the
AsyncHttpClient from Apache Http Components.

This is similar to the behavior I found when I ran the proxy standalone.
Of note is that it doesn't seem to fail if the tape is READ_WRITE. Will
have to add a test to demonstrate that as well, then refactor all the
duplicated code out.
  • Loading branch information
dkowis authored and David Kowis committed Jan 27, 2014
1 parent 11f2dd8 commit 6f1e717
Show file tree
Hide file tree
Showing 3 changed files with 562 additions and 0 deletions.
@@ -0,0 +1,128 @@
/*
* Copyright 2013 the original author or authors.
*
* 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.
*/

package co.freeside.betamax.proxy.concurrency

import co.freeside.betamax.ProxyConfiguration
import co.freeside.betamax.Recorder
import groovy.json.JsonSlurper
import org.apache.http.HttpHost
import org.apache.http.HttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.concurrent.FutureCallback
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.nio.client.HttpAsyncClients
import spock.lang.Specification

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

import static co.freeside.betamax.TapeMode.READ_ONLY
import static co.freeside.betamax.TapeMode.READ_WRITE

/**
* Testing a custom matcher when being used in the proxy.
*/
class ConcurrencyTest extends Specification {

def tapeRoot = new File(ConcurrencyTest.class.getResource("/betamax/tapes/").toURI())

void "When only playing back concurrently, it works"() {
given:
def proxyConfig = ProxyConfiguration.builder()
.tapeRoot(tapeRoot)
.defaultMode(READ_ONLY)
.defaultMatchRule(new PostingMatchRule())
.build()

def recorder = new Recorder(proxyConfig)
recorder.start("concurrentTape")

def builder = HttpAsyncClients.custom()
builder.setProxy(new HttpHost("127.0.0.1", 5555))

def httpClient = builder.build()

httpClient.start()
def requestCount = 10
println("client started, request count ${requestCount}")

List<HttpPost> requests = []

requestCount.times { num ->
def post = new HttpPost("http://httpbin.org/post")
post.setEntity(new StringEntity(num.toString(), ContentType.TEXT_PLAIN))
println("\tQueueing post $num")
requests.add post
}


when:
//Map the request body to the result body, they should match
def resultsMap = new ConcurrentHashMap<String, String>()

def latch = new CountDownLatch(requestCount)
println("Executing $requestCount requests concurrently")
requests.each { request ->
httpClient.execute(request, new FutureCallback<HttpResponse>() {
@Override
void completed(HttpResponse result) {
//HTTPBin will return what we post, so we'll just make sure that we save
// the request's body and the response body together for this result
// that way we can ensure that we're not clobbering stuff, since each request will have a different
// request body
//Using this to get the "data" attribute out
def json = new JsonSlurper().parseText(result.entity.content.text)

resultsMap.put(request.entity.content.text, json.data)
latch.countDown()
}

@Override
void failed(Exception ex) {
latch.countDown()
}

@Override
void cancelled() {
latch.countDown()
}
})
}

then:
//This should happen fast
println("Awaiting request completion with latch")
latch.await(5, TimeUnit.SECONDS)

println("Done, verifying things")
resultsMap.size() == requestCount

println("Verifying results map")
resultsMap.each { pair ->
//This assertion doesn't seem to work?
assert(pair.key == pair.value)
}

cleanup:
println("stopping recorder")
recorder?.stop()
println("Closing httpclient")
httpClient?.close()
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2013 the original author or authors.
*
* 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.
*/

package co.freeside.betamax.proxy.concurrency

import co.freeside.betamax.MatchRule
import co.freeside.betamax.message.Request

import java.util.concurrent.atomic.AtomicInteger

class PostingMatchRule implements MatchRule {

@Override
boolean isMatch(Request a, Request b) {
if (a.uri == b.uri && a.method == b.method) {
//Same method and URI, lets do a body comparison
//Can only consume the body once, once it's gone it's gone.
def aBody = a.bodyAsText.input.text
def bBody = b.bodyAsText.input.text

//Right now, lets just compare the bodies also
return aBody == bBody
} else {
//URI and method don't match, so we're going to bail
return false
}
}
}

0 comments on commit 6f1e717

Please sign in to comment.