func fakeFlushContext(t *testing.T, f func(req *pb.MemcacheFlushRequest, res *pb.MemcacheFlushResponse)) netcontext.Context {
	return aetesting.FakeSingleContext(t, "memcache", "FlushAll", func(req *pb.MemcacheFlushRequest, res *pb.MemcacheFlushResponse) error {
		f(req, res)
		return nil
	})
}
func TestAll(t *testing.T) {
	serviceCalled := false
	tests := []struct {
		desc    string
		ctx     netcontext.Context
		command string
		wants   []string
	}{
		{"get request",
			fakeGetContext(t, func(req *pb.MemcacheGetRequest, _ *pb.MemcacheGetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheGetRequest{
					NameSpace: proto.String(""),
					Key: [][]byte{
						[]byte("aKey"),
					},
					ForCas: proto.Bool(false),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"get aKey\r\n",
			[]string{},
		},
		{"gets request",
			fakeGetContext(t, func(req *pb.MemcacheGetRequest, _ *pb.MemcacheGetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheGetRequest{
					NameSpace: proto.String(""),
					Key: [][]byte{
						[]byte("aKey"),
					},
					ForCas: proto.Bool(true),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"gets aKey\r\n",
			[]string{},
		},
		{"multi get request",
			fakeGetContext(t, func(req *pb.MemcacheGetRequest, _ *pb.MemcacheGetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheGetRequest{
					NameSpace: proto.String(""),
					Key: [][]byte{
						[]byte("key0"),
						[]byte("key1"),
						[]byte("key2"),
					},
					ForCas: proto.Bool(false),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"get key0 key1 key2\r\n",
			[]string{},
		},
		{"get response",
			fakeGetContext(t, func(_ *pb.MemcacheGetRequest, res *pb.MemcacheGetResponse) {
				res.Item = append(res.Item, &pb.MemcacheGetResponse_Item{
					Key:   []byte("aKey"),
					Value: []byte("some value"),
					Flags: proto.Uint32(111),
				})
			}),
			"get aKey\r\n",
			[]string{
				"VALUE aKey 111 10\r\n",
				"some value\r\n",
				"END\r\n",
				timeoutMarker,
			},
		},
		{"gets response with cas",
			fakeGetContext(t, func(_ *pb.MemcacheGetRequest, res *pb.MemcacheGetResponse) {
				res.Item = append(res.Item, &pb.MemcacheGetResponse_Item{
					Key:   []byte("aKey"),
					Value: []byte("some value"),
					Flags: proto.Uint32(111),
					CasId: proto.Uint64(31415),
				})
			}),
			"gets aKey\r\n",
			[]string{
				"VALUE aKey 111 10 31415\r\n",
				"some value\r\n",
				"END\r\n",
				timeoutMarker,
			},
		},
		{"get miss",
			fakeGetContext(t, func(_ *pb.MemcacheGetRequest, res *pb.MemcacheGetResponse) {}),
			"get aKey\r\n",
			[]string{
				"END\r\n",
				timeoutMarker,
			},
		},
		{"multi get response",
			fakeGetContext(t, func(_ *pb.MemcacheGetRequest, res *pb.MemcacheGetResponse) {
				res.Item = append(res.Item, &pb.MemcacheGetResponse_Item{
					Key:   []byte("key1"),
					Value: []byte("some value"),
					Flags: proto.Uint32(111),
				})
				res.Item = append(res.Item, &pb.MemcacheGetResponse_Item{
					Key:   []byte("key2"),
					Value: []byte("another value"),
					Flags: proto.Uint32(222),
				})
			}),
			"get key1 key2\r\n",
			[]string{
				"VALUE key1 111 10\r\n",
				"some value\r\n",
				"VALUE key2 222 13\r\n",
				"another value\r\n",
				"END\r\n",
				timeoutMarker,
			},
		},
		{"get empty item",
			fakeGetContext(t, func(_ *pb.MemcacheGetRequest, res *pb.MemcacheGetResponse) {
				// Return empty item in response.
				res.Item = append(res.Item, &pb.MemcacheGetResponse_Item{})
			}),
			"get aKey\r\n",
			[]string{
				"SERVER_ERROR \"no key returned from server\"\r\n",
				timeoutMarker,
			},
		},
		{"get no keys",
			getNeverCalled,
			"get\r\n", // missing keys
			[]string{
				"ERROR\r\n",
				timeoutMarker,
			},
		},
		{"get server error",
			aetesting.FakeSingleContext(t, "memcache", "Get", func(_ *pb.MemcacheGetRequest, _ *pb.MemcacheGetResponse) error {
				return fmt.Errorf("some server error")
			}),
			"get aKey\r\n",
			[]string{
				"SERVER_ERROR \"some server error\"\r\n",
				timeoutMarker,
			},
		},
		{"delete response",
			fakeDeleteContext(t, func(_ *pb.MemcacheDeleteRequest, res *pb.MemcacheDeleteResponse) {
				res.DeleteStatus = append(res.DeleteStatus, pb.MemcacheDeleteResponse_DELETED)
			}),
			"delete aKey\r\n",
			[]string{
				"DELETED\r\n",
				timeoutMarker,
			},
		},
		{"delete no key",
			getNeverCalled,
			"delete\r\n", // missing keys
			[]string{
				"ERROR\r\n",
				timeoutMarker,
			},
		},
		{"delete no key noreply",
			getNeverCalled,
			"delete noreply\r\n", // missing keys
			[]string{
				"ERROR\r\n",
				timeoutMarker,
			},
		},
		{"increment response",
			fakeIncrContext(t, func(_ *pb.MemcacheIncrementRequest, res *pb.MemcacheIncrementResponse) {
				res.IncrementStatus = pb.MemcacheIncrementResponse_OK.Enum()
				res.NewValue = proto.Uint64(315)
			}),
			"incr aKey 1\r\n",
			[]string{
				"315\r\n",
				timeoutMarker,
			},
		},
		{"increment response noreply",
			fakeIncrContext(t, func(_ *pb.MemcacheIncrementRequest, res *pb.MemcacheIncrementResponse) {
				res.IncrementStatus = pb.MemcacheIncrementResponse_OK.Enum()
				res.NewValue = proto.Uint64(315)
			}),
			"incr aKey 1 noreply\r\n",
			[]string{
				timeoutMarker,
			},
		},
		{"increment response error",
			fakeIncrContext(t, func(_ *pb.MemcacheIncrementRequest, res *pb.MemcacheIncrementResponse) {
				res.IncrementStatus = pb.MemcacheIncrementResponse_ERROR.Enum()
			}),
			"incr aKey 1\r\n",
			[]string{
				"ERROR\r\n",
				timeoutMarker,
			},
		},
		{"set request",
			fakeSetContext(t, func(req *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheSetRequest{
					NameSpace: proto.String(""),
					Item: []*pb.MemcacheSetRequest_Item{
						{
							Key:            []byte("aKey"),
							Value:          []byte("some value"),
							Flags:          proto.Uint32(111),
							SetPolicy:      pb.MemcacheSetRequest_SET.Enum(),
							ExpirationTime: proto.Uint32(3600),
						},
					},
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"set aKey 111 3600 10\r\nsome value\r\n",
			[]string{},
		},
		{"set request max memcache size ",
			fakeSetContext(t, func(req *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheSetRequest{
					NameSpace: proto.String(""),
					Item: []*pb.MemcacheSetRequest_Item{
						{
							Key:            []byte("aMaxPayloadKey"),
							Value:          []byte(maxMemcachePayloadValue),
							Flags:          proto.Uint32(111),
							SetPolicy:      pb.MemcacheSetRequest_SET.Enum(),
							ExpirationTime: proto.Uint32(3600),
						},
					},
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"set aMaxPayloadKey 111 3600 " + strconv.Itoa(len(maxMemcachePayloadValue)) + "\r\n" + maxMemcachePayloadValue + "\r\n",
			[]string{},
		},
		{"set request with whitespace",
			fakeSetContext(t, func(req *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheSetRequest{
					NameSpace: proto.String(""),
					Item: []*pb.MemcacheSetRequest_Item{
						{
							Key:            []byte("anotherKey"),
							Value:          []byte("ANOTHER VALUE"),
							Flags:          proto.Uint32(22222),
							SetPolicy:      pb.MemcacheSetRequest_SET.Enum(),
							ExpirationTime: proto.Uint32(7200),
						},
					},
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			// Make sure we can handle extra tabs and blanks.
			"   set   anotherKey\t22222\t\t7200    13   \r\nANOTHER VALUE\r\n",
			[]string{},
		},
		{"set response",
			setStored,
			"set aKey 111 3600 10\r\nsome value\r\n",
			[]string{
				"STORED\r\n",
				timeoutMarker,
			},
		},
		{"set response no reply",
			setStored,
			"set aKey 111 3600 10 noreply\r\nsome value\r\n",
			[]string{
				timeoutMarker,
			},
		},
		{"set response bogus no reply",
			setStored,
			// Instead of "noreply" add bogus token
			"set aKey 111 3600 10 aBogusToken\r\nsome value\r\n",
			[]string{
				"STORED\r\n",
				timeoutMarker,
			},
		},
		{"set wrong number of tokens",
			setNeverCalled,
			"set foo 0 0\r\n", // missing bytes token
			[]string{
				"ERROR\r\n",
				timeoutMarker,
			},
		},
		{"set bad flags",
			setNeverCalled,
			"set foo xxxx 0 5\r\n", // non-integer flags
			[]string{
				"CLIENT_ERROR bad command line format\r\n",
				timeoutMarker,
			},
		},
		{"set bad expiry",
			setNeverCalled,
			"set foo 0 xxxx 5\r\n", // non-integer expiry
			[]string{
				"CLIENT_ERROR bad command line format\r\n",
				timeoutMarker,
			},
		},
		{"set bad termination",
			setNeverCalled,
			"set aKey 111 3600 10\r\nsome valueXX", // XX instead of \r\n
			[]string{
				"CLIENT_ERROR bad data chunk\r\n",
				timeoutMarker,
			},
		},
		{"set empty response",
			fakeSetContext(t, func(_ *pb.MemcacheSetRequest, res *pb.MemcacheSetResponse) {}),
			"set aKey 111 3600 10\r\nsome value\r\n",
			[]string{
				"SERVER_ERROR \"server did not return a status\"\r\n",
				timeoutMarker,
			},
		},
		{"set server error",
			aetesting.FakeSingleContext(t, "memcache", "Set", func(_ *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) error {
				return fmt.Errorf("some server error")
			}),
			"set aKey 111 3600 10\r\nsome value\r\n",
			[]string{
				"SERVER_ERROR \"some server error\"\r\n",
				timeoutMarker,
			},
		},
		{"add request",
			fakeSetContext(t, func(req *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheSetRequest{
					NameSpace: proto.String(""),
					Item: []*pb.MemcacheSetRequest_Item{
						{
							Key:            []byte("aKey"),
							Value:          []byte("some value"),
							Flags:          proto.Uint32(111),
							SetPolicy:      pb.MemcacheSetRequest_ADD.Enum(),
							ExpirationTime: proto.Uint32(3600),
						},
					},
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"add aKey 111 3600 10\r\nsome value\r\n",
			[]string{},
		},
		{"replace request",
			fakeSetContext(t, func(req *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheSetRequest{
					NameSpace: proto.String(""),
					Item: []*pb.MemcacheSetRequest_Item{
						{
							Key:            []byte("aKey"),
							Value:          []byte("some value"),
							Flags:          proto.Uint32(111),
							SetPolicy:      pb.MemcacheSetRequest_REPLACE.Enum(),
							ExpirationTime: proto.Uint32(3600),
						},
					},
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"replace aKey 111 3600 10\r\nsome value\r\n",
			[]string{},
		},
		{"cas request",
			fakeSetContext(t, func(req *pb.MemcacheSetRequest, _ *pb.MemcacheSetResponse) {
				serviceCalled = true
				// Test request.
				want := &pb.MemcacheSetRequest{
					NameSpace: proto.String(""),
					Item: []*pb.MemcacheSetRequest_Item{
						{
							Key:            []byte("aKey"),
							Value:          []byte("some value"),
							Flags:          proto.Uint32(111),
							SetPolicy:      pb.MemcacheSetRequest_CAS.Enum(),
							CasId:          proto.Uint64(31415926535),
							ExpirationTime: proto.Uint32(3600),
							ForCas:         proto.Bool(true),
						},
					},
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"cas aKey 111 3600 10 31415926535\r\nsome value\r\n",
			[]string{},
		},
		{"incr request",
			fakeIncrContext(t, func(req *pb.MemcacheIncrementRequest, _ *pb.MemcacheIncrementResponse) {
				serviceCalled = true
				want := &pb.MemcacheIncrementRequest{
					Key:       []byte("aKey"),
					Delta:     proto.Uint64(314),
					NameSpace: proto.String(""),
					Direction: pb.MemcacheIncrementRequest_INCREMENT.Enum(),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"incr aKey 314\r\n",
			[]string{},
		},
		{"incr request no arg",
			fakeIncrContext(t, func(req *pb.MemcacheIncrementRequest, _ *pb.MemcacheIncrementResponse) {
				serviceCalled = true
				want := &pb.MemcacheIncrementRequest{
					Key:       []byte("aKey"),
					Delta:     proto.Uint64(1),
					NameSpace: proto.String(""),
					Direction: pb.MemcacheIncrementRequest_INCREMENT.Enum(),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"incr aKey\r\n",
			[]string{},
		},
		{"decr request",
			fakeIncrContext(t, func(req *pb.MemcacheIncrementRequest, _ *pb.MemcacheIncrementResponse) {
				serviceCalled = true
				want := &pb.MemcacheIncrementRequest{
					Key:       []byte("aKey"),
					Delta:     proto.Uint64(314),
					NameSpace: proto.String(""),
					Direction: pb.MemcacheIncrementRequest_DECREMENT.Enum(),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"decr aKey 314\r\n",
			[]string{},
		},
		{"decr request no arg",
			fakeIncrContext(t, func(req *pb.MemcacheIncrementRequest, _ *pb.MemcacheIncrementResponse) {
				serviceCalled = true
				want := &pb.MemcacheIncrementRequest{
					Key:       []byte("aKey"),
					Delta:     proto.Uint64(1),
					NameSpace: proto.String(""),
					Direction: pb.MemcacheIncrementRequest_DECREMENT.Enum(),
				}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"decr aKey\r\n",
			[]string{},
		},
		{"flush request with ignored ts",
			fakeFlushContext(t, func(req *pb.MemcacheFlushRequest, _ *pb.MemcacheFlushResponse) {
				serviceCalled = true
				want := &pb.MemcacheFlushRequest{}
				if !proto.Equal(req, want) {
					t.Errorf("got  <%s>\nwant <%s>",
						proto.MarshalTextString(req),
						proto.MarshalTextString(want))
				}
			}),
			"flush_all 123 noreply\r\n",
			[]string{},
		},
		{"flush response always OK",
			fakeFlushContext(t, func(req *pb.MemcacheFlushRequest, _ *pb.MemcacheFlushResponse) {
			}),
			"flush_all 123\r\n",
			[]string{"OK\r\n",
				timeoutMarker,
			},
		},
		{"flush no argument",
			fakeFlushContext(t, func(req *pb.MemcacheFlushRequest, _ *pb.MemcacheFlushResponse) {
			}),
			"flush_all\r\n",
			[]string{"OK\r\n",
				timeoutMarker,
			},
		},
		{"stats request",
			fakeStatsContext(t, func(req *pb.MemcacheStatsRequest, _ *pb.MemcacheStatsResponse) {
				serviceCalled = true
			}),
			"stats\r\n",
			[]string{},
		},
		{"stats response",
			fakeStatsContext(t, func(_ *pb.MemcacheStatsRequest, res *pb.MemcacheStatsResponse) {
				res.Stats = &pb.MergedNamespaceStats{
					Hits:          proto.Uint64(111),
					Misses:        proto.Uint64(222),
					ByteHits:      proto.Uint64(333),
					Items:         proto.Uint64(444),
					Bytes:         proto.Uint64(555),
					OldestItemAge: proto.Uint32(666),
				}
			}),
			"stats\r\n",
			[]string{
				"STAT get_hits 111\r\n",
				"STAT get_misses 222\r\n",
				"STAT bytes_read 333\r\n",
				"STAT curr_items 444\r\n",
				"STAT bytes 555\r\n",
				"STAT oldest_item_age 666\r\n",
				"STAT version App Engine\r\n",
				"END\r\n",
				timeoutMarker,
			},
		},
		{"stats missing",
			// TODO(eobrain) Check that it really is OK
			// and expected for the memcache_service to
			// have missing stats, which is the case after
			// a flush.
			fakeStatsContext(t, func(_ *pb.MemcacheStatsRequest, res *pb.MemcacheStatsResponse) {}),
			"stats\r\n",
			[]string{
				"END\r\n",
				timeoutMarker,
			},
		},
		{"stats server error",
			aetesting.FakeSingleContext(t, "memcache", "Stats", func(_ *pb.MemcacheStatsRequest, _ *pb.MemcacheStatsResponse) error {
				return fmt.Errorf("some server error")
			}),
			"stats\r\n",
			[]string{
				"SERVER_ERROR \"some server error\"\r\n",
				timeoutMarker,
			},
		},
		{"empty line",
			statsNeverCalled,
			"\r\n", // Send a blank line
			[]string{
				"ERROR\r\n",
				timeoutMarker,
			},
		},
		{"version request",
			nil,
			"version\r\n",
			[]string{
				"VERSION App Engine\r\n",
				timeoutMarker,
			},
		},
	}

	for i, test := range tests {
		serviceCalled = false
		log.Printf("----- Begin: %s", test.desc)
		gots := sendCommand(test.ctx, test.command, len(test.wants))
		for i, want := range test.wants {
			if gots[i] != want {
				t.Errorf("test %d, %s: got %q want %q", i, test.desc, gots[i], want)
			}
		}
		// If not expecting any output, make sure
		// serviceCalled flag was set by context.
		if len(test.wants) == 0 && !serviceCalled {
			t.Errorf("test %d, %s: service was not called as expected", i, test.desc)
		}
		log.Printf("----- End: %s", test.desc)
	}
}
func fakeIncrContext(t *testing.T, f func(req *pb.MemcacheIncrementRequest, res *pb.MemcacheIncrementResponse)) netcontext.Context {
	return aetesting.FakeSingleContext(t, "memcache", "Increment", func(req *pb.MemcacheIncrementRequest, res *pb.MemcacheIncrementResponse) error {
		f(req, res)
		return nil
	})
}