Skip to content

Commit df4c740

Browse files
jcrossleyjenkins
authored and
jenkins
committed
finagle/finagle-memcached: Refactor tests
Problem Testing of the protocol vs general client behaviour is scattered. Solution Simplify and unify the testing. Differential Revision: https://phabricator.twitter.biz/D1179085
1 parent c9bad12 commit df4c740

19 files changed

+1107
-1584
lines changed

finagle-memcached/src/test/java/com/twitter/finagle/memcached/integration/TestClient.java

+5-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import scala.Option;
44

5-
import org.junit.Assume;
65
import org.junit.Before;
76
import org.junit.Test;
87

@@ -13,8 +12,8 @@
1312
import com.twitter.finagle.Service;
1413
import com.twitter.finagle.memcached.JavaClient;
1514
import com.twitter.finagle.memcached.JavaClientBase;
16-
import com.twitter.finagle.memcached.integration.external.TestMemcachedServer;
17-
import com.twitter.finagle.memcached.integration.external.TestMemcachedServer$;
15+
import com.twitter.finagle.memcached.integration.external.MemcachedServer;
16+
import com.twitter.finagle.memcached.integration.external.MemcachedServer$;
1817
import com.twitter.finagle.memcached.protocol.Command;
1918
import com.twitter.finagle.memcached.protocol.Response;
2019
import com.twitter.io.Bufs;
@@ -23,20 +22,19 @@
2322
import static org.junit.Assert.assertEquals;
2423

2524
public class TestClient {
26-
private Option<TestMemcachedServer> server;
25+
private MemcachedServer server;
2726

2827
@Before
2928
public void setUp() {
30-
server = TestMemcachedServer$.MODULE$.start();
31-
Assume.assumeTrue(server.isDefined());
29+
server = MemcachedServer$.MODULE$.start();
3230
}
3331

3432
/**
3533
* Tests Get/Set commands.
3634
*/
3735
@Test
3836
public void testGetAndSet() throws Exception {
39-
Address addr = Addresses.newInetAddress(server.get().address());
37+
Address addr = Addresses.newInetAddress(server.address());
4038

4139
Service<Command, Response> service = Memcached.client()
4240
.connectionsPerEndpoint(1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
package com.twitter.finagle.memcached.integration
2+
3+
import com.twitter.conversions.DurationOps._
4+
import com.twitter.finagle.Address
5+
import com.twitter.finagle.Name
6+
import com.twitter.finagle.memcached.Client
7+
import com.twitter.finagle.memcached.integration.external.ExternalMemcached
8+
import com.twitter.finagle.memcached.integration.external.MemcachedServer
9+
import com.twitter.finagle.memcached.protocol._
10+
import com.twitter.io.Buf
11+
import com.twitter.util.Await
12+
import com.twitter.util.Awaitable
13+
import org.scalatest.BeforeAndAfter
14+
import org.scalatest.funsuite.AnyFunSuite
15+
16+
// These tests run against either a test Memcached server or a real MemcachedServer,
17+
// depending on the value of `ExternalMemcached.use`
18+
abstract class ClientAPITest extends AnyFunSuite with BeforeAndAfter {
19+
20+
protected var client: Client = _
21+
private var testServer: MemcachedServer = _
22+
23+
protected def awaitResult[T](awaitable: Awaitable[T]): T = Await.result(awaitable, 5.seconds)
24+
25+
protected def mkClient(dest: Name): Client
26+
27+
before {
28+
testServer = MemcachedServer.start()
29+
client = mkClient(Name.bound(Seq(Address(testServer.address)): _*))
30+
}
31+
32+
after {
33+
testServer.stop()
34+
}
35+
36+
test("set & get") {
37+
awaitResult(client.delete("foo"))
38+
assert(awaitResult(client.get("foo")) == None)
39+
awaitResult(client.set("foo", Buf.Utf8("bar")))
40+
assert(awaitResult(client.get("foo")).get == Buf.Utf8("bar"))
41+
}
42+
43+
test("set & get data containing newlines") {
44+
awaitResult(client.delete("bob"))
45+
assert(awaitResult(client.get("bob")) == None)
46+
awaitResult(client.set("bob", Buf.Utf8("hello there \r\n nice to meet \r\n you")))
47+
assert(
48+
awaitResult(client.get("bob")).get == Buf.Utf8("hello there \r\n nice to meet \r\n you"),
49+
3.seconds
50+
)
51+
}
52+
53+
test("get") {
54+
awaitResult(client.set("foo", Buf.Utf8("bar")))
55+
awaitResult(client.set("baz", Buf.Utf8("boing")))
56+
val result = awaitResult(client.get(Seq("foo", "baz", "notthere")))
57+
.map { case (key, Buf.Utf8(value)) => (key, value) }
58+
assert(
59+
result == Map(
60+
"foo" -> "bar",
61+
"baz" -> "boing"
62+
)
63+
)
64+
}
65+
66+
test("getWithFlag") {
67+
awaitResult(client.set("foo", Buf.Utf8("bar")))
68+
awaitResult(client.set("baz", Buf.Utf8("boing")))
69+
val result = awaitResult(client.getWithFlag(Seq("foo", "baz", "notthere")))
70+
.map { case (key, ((Buf.Utf8(value), Buf.Utf8(flag)))) => (key, (value, flag)) }
71+
assert(
72+
result == Map(
73+
"foo" -> (("bar", "0")),
74+
"baz" -> (("boing", "0"))
75+
)
76+
)
77+
}
78+
79+
test("append & prepend") {
80+
awaitResult(client.set("foo", Buf.Utf8("bar")))
81+
awaitResult(client.append("foo", Buf.Utf8("rab")))
82+
val Buf.Utf8(res) = awaitResult(client.get("foo")).get
83+
assert(res == "barrab")
84+
awaitResult(client.prepend("foo", Buf.Utf8("rab")))
85+
val Buf.Utf8(res2) = awaitResult(client.get("foo")).get
86+
assert(res2 == "rabbarrab")
87+
}
88+
89+
test("incr & decr") {
90+
// As of memcached 1.4.8 (issue 221), empty values are no longer treated as integers
91+
awaitResult(client.set("foo", Buf.Utf8("0")))
92+
assert(awaitResult(client.incr("foo")) == Some(1L))
93+
assert(awaitResult(client.incr("foo", 2)) == Some(3L))
94+
assert(awaitResult(client.decr("foo")) == Some(2L))
95+
96+
awaitResult(client.set("foo", Buf.Utf8("0")))
97+
assert(awaitResult(client.incr("foo")) == Some(1L))
98+
val l = 1L << 50
99+
assert(awaitResult(client.incr("foo", l)) == Some(l + 1L))
100+
assert(awaitResult(client.decr("foo")) == Some(l))
101+
assert(awaitResult(client.decr("foo", l)) == Some(0L))
102+
}
103+
104+
test("send malformed keys") {
105+
// test key validation trait
106+
intercept[ClientError] {
107+
awaitResult(client.get("fo o"))
108+
}
109+
intercept[ClientError] {
110+
awaitResult(client.set("", Buf.Utf8("bar")))
111+
}
112+
intercept[ClientError] {
113+
awaitResult(client.get(" foo"))
114+
}
115+
intercept[ClientError] {
116+
awaitResult(client.get("foo "))
117+
}
118+
intercept[ClientError] {
119+
awaitResult(client.get(" foo"))
120+
}
121+
intercept[ClientError] {
122+
awaitResult(client.get(null: String))
123+
}
124+
intercept[ClientError] {
125+
awaitResult(client.set(null: String, Buf.Utf8("bar")))
126+
}
127+
intercept[ClientError] {
128+
awaitResult(client.set(" ", Buf.Utf8("bar")))
129+
}
130+
131+
try {
132+
awaitResult(client.set("\t", Buf.Utf8("bar")))
133+
} catch {
134+
case _: ClientError => fail("\t is allowed")
135+
}
136+
137+
intercept[ClientError] {
138+
awaitResult(client.set("\r", Buf.Utf8("bar")))
139+
}
140+
intercept[ClientError] {
141+
awaitResult(client.set("\n", Buf.Utf8("bar")))
142+
}
143+
intercept[ClientError] {
144+
awaitResult(client.set("\u0000", Buf.Utf8("bar")))
145+
}
146+
147+
val veryLongKey =
148+
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
149+
intercept[ClientError] {
150+
awaitResult(client.get(veryLongKey))
151+
}
152+
intercept[ClientError] {
153+
awaitResult(client.set(veryLongKey, Buf.Utf8("bar")))
154+
}
155+
156+
// test other keyed command validation
157+
intercept[ClientError] {
158+
awaitResult(client.gets(Seq(null)))
159+
}
160+
intercept[ClientError] {
161+
awaitResult(client.gets(Seq("")))
162+
}
163+
intercept[ClientError] {
164+
awaitResult(client.gets(Seq("foos", "bad key", "somethingelse")))
165+
}
166+
intercept[ClientError] {
167+
awaitResult(client.append("bad key", Buf.Utf8("rab")))
168+
}
169+
intercept[ClientError] {
170+
awaitResult(client.prepend("bad key", Buf.Utf8("rab")))
171+
}
172+
intercept[ClientError] {
173+
awaitResult(client.replace("bad key", Buf.Utf8("bar")))
174+
}
175+
intercept[ClientError] {
176+
awaitResult(client.add("bad key", Buf.Utf8("2")))
177+
}
178+
intercept[ClientError] {
179+
awaitResult(client.checkAndSet("bad key", Buf.Utf8("z"), Buf.Utf8("2")))
180+
}
181+
intercept[ClientError] {
182+
awaitResult(client.incr("bad key"))
183+
}
184+
intercept[ClientError] {
185+
awaitResult(client.decr("bad key"))
186+
}
187+
intercept[ClientError] {
188+
awaitResult(client.delete("bad key"))
189+
}
190+
}
191+
192+
// Only run these if we have a real memcached server to use
193+
if (ExternalMemcached.use()) {
194+
test("gets") {
195+
assert(awaitResult(client.gets("foos")).isEmpty)
196+
awaitResult(client.set("foos", Buf.Utf8("xyz")))
197+
awaitResult(client.set("bazs", Buf.Utf8("xyz")))
198+
awaitResult(client.set("bazs", Buf.Utf8("zyx")))
199+
val result = awaitResult(client.gets(Seq("foos", "bazs", "somethingelse")))
200+
.map {
201+
case (key, (Buf.Utf8(value), Buf.Utf8(casUnique))) =>
202+
(key, (value, casUnique))
203+
}
204+
205+
assert(
206+
result == Map(
207+
"foos" -> (
208+
(
209+
"xyz",
210+
"2"
211+
)
212+
), // the "cas unique" values are predictable from a fresh memcached
213+
"bazs" -> (("zyx", "4"))
214+
)
215+
)
216+
}
217+
218+
test("getsWithFlag") {
219+
awaitResult(client.set("foos1", Buf.Utf8("xyz")))
220+
awaitResult(client.set("bazs1", Buf.Utf8("xyz")))
221+
awaitResult(client.set("bazs1", Buf.Utf8("zyx")))
222+
val result =
223+
awaitResult(client.getsWithFlag(Seq("foos1", "bazs1", "somethingelse")))
224+
.map {
225+
case (key, (Buf.Utf8(value), Buf.Utf8(flag), Buf.Utf8(casUnique))) =>
226+
(key, (value, flag, casUnique))
227+
}
228+
229+
// the "cas unique" values are predictable from a fresh memcached
230+
assert(
231+
result == Map(
232+
"foos1" -> (("xyz", "0", "2")),
233+
"bazs1" -> (("zyx", "0", "4"))
234+
)
235+
)
236+
}
237+
238+
test("cas") {
239+
awaitResult(client.set("x", Buf.Utf8("y")))
240+
val Some((value, casUnique)) = awaitResult(client.gets("x"))
241+
assert(value == Buf.Utf8("y"))
242+
assert(casUnique == Buf.Utf8("2"))
243+
244+
assert(!awaitResult(client.checkAndSet("x", Buf.Utf8("z"), Buf.Utf8("1")).map(_.replaced)))
245+
assert(
246+
awaitResult(client.checkAndSet("x", Buf.Utf8("z"), casUnique).map(_.replaced)).booleanValue
247+
)
248+
val res = awaitResult(client.get("x"))
249+
assert(res.isDefined)
250+
assert(res.get == Buf.Utf8("z"))
251+
}
252+
}
253+
}

0 commit comments

Comments
 (0)