Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Created a failing test

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...
commit 6f1e7173c5d2684bc5914dd8e21013f033de37ef 1 parent 11f2dd8
@dkowis dkowis authored dkowis committed
View
128 betamax-proxy/src/test/groovy/co/freeside/betamax/proxy/concurrency/ConcurrencyTest.groovy
@@ -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()
+ }
+}
View
41 betamax-proxy/src/test/groovy/co/freeside/betamax/proxy/concurrency/PostingMatchRule.groovy
@@ -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
+ }
+ }
+}
View
393 betamax-proxy/src/test/resources/betamax/tapes/concurrentTape.yaml
@@ -0,0 +1,393 @@
+!tape
+name: concurrentTape
+interactions:
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '0'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "0",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '1'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "1",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '2'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "2",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '3'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "3",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '4'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "4",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '5'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "5",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '6'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "6",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '7'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "7",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '8'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "8",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
+- recorded: 2014-01-27T01:03:19.972Z
+ request:
+ method: POST
+ uri: http://httpbin.org/post
+ headers:
+ Content-Length: '1'
+ Content-Type: text/plain; charset=ISO-8859-1
+ Host: httpbin.org
+ Proxy-Connection: Keep-Alive
+ User-Agent: Apache-HttpAsyncClient/4.0 (java 1.5)
+ body: '9'
+ response:
+ status: 200
+ headers:
+ Access-Control-Allow-Origin: '*'
+ Connection: keep-alive
+ Content-Length: '435'
+ Content-Type: application/json
+ Date: Mon, 27 Jan 2014 01:03:19 GMT
+ Server: gunicorn/0.17.4
+ body: |-
+ {
+ "args": {},
+ "url": "http://httpbin.org/post",
+ "files": {},
+ "headers": {
+ "X-Request-Id": "ce99c82f-c429-4627-96ee-b394409a03b8",
+ "Host": "httpbin.org",
+ "User-Agent": "Apache-HttpAsyncClient/4.0 (java 1.5)",
+ "Content-Type": "text/plain; charset=ISO-8859-1",
+ "Connection": "close",
+ "Via": "Betamax",
+ "Content-Length": "1"
+ },
+ "data": "9",
+ "json": 0,
+ "origin": "173.175.114.180",
+ "form": {}
+ }
Please sign in to comment.
Something went wrong with that request. Please try again.