Compare commits

...

159 Commits
master ... ng

Author SHA1 Message Date
Pieter Noordhuis
0164332a99 Remove leading _ from include guard 2012-10-23 11:20:28 -07:00
Pieter Noordhuis
18b01764fd Use AF_UNIX in test 2012-10-23 11:17:59 -07:00
Pieter Noordhuis
1680eaf357 AF_UNIX seems to be more widely accepted than AF_LOCAL 2012-08-14 09:51:55 -07:00
Pieter Noordhuis
e54def88e6 Define POSIX.1-2001 2012-08-14 09:45:52 -07:00
Pieter Noordhuis
3ce256cf7b Fix library dependency in test Makefile 2012-08-14 09:44:30 -07:00
Pieter Noordhuis
f0f02ba162 POSIX.1-2001 declares usleep obsolete; use nanosleep 2012-08-14 09:42:51 -07:00
Pieter Noordhuis
4e9221f3b3 Include <sys/types.h> for kill(2) on Linux 2012-08-14 09:36:17 -07:00
Pieter Noordhuis
e1c922096c Include Makefile.dep 2012-08-14 09:30:16 -07:00
Pieter Noordhuis
5f1ed96692 Order of includes doesn't matter in tests either 2012-08-14 09:28:51 -07:00
Pieter Noordhuis
d0b6b9ab76 Remove fmacros.h 2012-08-14 09:26:09 -07:00
Pieter Noordhuis
c923fdf06f sockaddr_in.sin_len is a BSD-ism 2012-08-14 09:10:55 -07:00
Pieter Noordhuis
7532d85081 Order of includes is not something to care about 2012-08-14 08:57:12 -07:00
Pieter Noordhuis
16ea09090a Prefix header guards with _ 2012-08-14 08:54:16 -07:00
Pieter Noordhuis
2b61fd1a28 Replace select(2) with poll(2)
Like f8debbfdbe.
2012-08-14 08:50:04 -07:00
Pieter Noordhuis
1f6a4d8c6a Extract conversion specifiers to variable
Like 56ae8aa110.
2012-08-13 18:24:48 -07:00
Pieter Noordhuis
1ab4fc95f2 Merge branch 'ng-src' into ng 2012-06-08 17:39:55 -07:00
Pieter Noordhuis
fde8520274 Adapt tests to new source layout 2012-06-08 16:32:08 -07:00
Pieter Noordhuis
e4df5892ea Add more common variables 2012-06-08 16:30:51 -07:00
Pieter Noordhuis
2afcc9c76e Move common Makefile variables to separate file 2012-06-08 16:28:54 -07:00
Pieter Noordhuis
3a91923d61 Move new bits to src/ 2012-06-08 15:49:25 -07:00
Pieter Noordhuis
993981a0d3 Keep track of bytes per callback
This allows to drop the requirement that the request itself is
responsible for keeping track of the number of bytes it has emitted via
`write_ptr` and the number of bytes that were actually written in
`write_cb`. The request queue now lets the request know how many bytes
have been written on its account, instead of asking for it.
2012-03-06 13:20:19 -08:00
Pieter Noordhuis
2ef4a5be15 Test write callback for empty requests 2012-03-06 11:56:03 -08:00
Pieter Noordhuis
50c41814a8 Factor out test setup macro 2012-03-06 11:55:06 -08:00
Pieter Noordhuis
2a37c4a100 Allow sentinel requests in request queue 2012-03-06 11:39:59 -08:00
Pieter Noordhuis
d991528368 Use default parser callbacks for request queue 2012-03-06 11:05:37 -08:00
Pieter Noordhuis
b71887216d Request queue is responsible for parsing replies
The change in d0eb3f65 moved responsibility to the request itself, but
kept ownership of the parser struct with the request queue. In
retrospect, it is cleaner to keep protocol parsing in the queue and have
it pass the parsed protocol via the read callback.
2012-03-06 11:05:32 -08:00
Pieter Noordhuis
796b2f1f51 Add I/O hooks to request queue 2012-03-05 23:42:28 -08:00
Pieter Noordhuis
b2dff31807 Remove unnecessary condition 2012-03-05 23:42:28 -08:00
Pieter Noordhuis
5146cf994b Remove obsolete function 2012-03-05 18:32:58 -08:00
Pieter Noordhuis
db1b4e9659 Prefix request queue callbacks 2012-03-05 16:24:20 -08:00
Pieter Noordhuis
ef8f0a613b Fail if the fd is out of select(2) range 2012-01-20 08:11:10 -08:00
Pieter Noordhuis
2defe23277 Extract fd r/w to separate file 2012-01-10 14:27:46 -08:00
Pieter Noordhuis
f7097a170e Extract file descriptor operations to separate file 2012-01-10 14:16:44 -08:00
Pieter Noordhuis
fce264e385 Test *_done fields being set on request struct 2012-01-10 13:13:48 -08:00
Pieter Noordhuis
125cf58cec Simultaneous writes and reads for requests in the queue
Every request is now kept in a maximum of two queues. When a request is
initially inserted, it is inserted in the `to_write` queue. When at
least one write buffer is extracted from that request, it is *moved* to
the `wait_write` queue. When at least one byte of the request has been
written to the socket, it is *also* placed in the `wait_read` queue.

This means that it is possible for a request to be emitting write
buffers, while simultaneously receiving read callbacks.
2012-01-10 12:01:05 -08:00
Pieter Noordhuis
6138ef4aca Use done ptr to signal done in write_ptr function 2012-01-10 09:54:13 -08:00
Pieter Noordhuis
d0eb3f6511 Request is responsible for parsing its own reply 2012-01-10 09:24:44 -08:00
Pieter Noordhuis
6c990440ee No return value for request queue insert 2012-01-09 16:44:51 -08:00
Pieter Noordhuis
3be5869671 Private data field for request queue struct 2012-01-09 15:36:15 -08:00
Pieter Noordhuis
8e7745605e Ignore test binaries 2012-01-06 18:07:55 -08:00
Pieter Noordhuis
85dac0d63f Don't warn when test helper functions are unused 2012-01-06 18:07:45 -08:00
Pieter Noordhuis
afb462698f Store Makefile dependencies in separate file 2012-01-06 17:51:14 -08:00
Pieter Noordhuis
ee39aff4bc Less restrictive ignores 2012-01-06 17:30:38 -08:00
Pieter Noordhuis
ee99d5a3d1 Request queue
The request queue abstraction intends to provide an interface usable
by asynchronous I/O libraries, without making assumptions about their
interfaces.
2012-01-06 17:29:34 -08:00
Pieter Noordhuis
87e9451e6b Include stdarg.h for va_list 2012-01-06 11:02:16 -08:00
Pieter Noordhuis
7ad207fb6d Make ignoring connection in test more explicit 2012-01-05 14:41:07 -08:00
Pieter Noordhuis
945aae00d8 Test reading EOF 2012-01-05 14:22:04 -08:00
Pieter Noordhuis
0a66b7c02b Rename test helper to avoid naming conflict 2012-01-05 14:15:10 -08:00
Pieter Noordhuis
4757c6d150 Consistently use named variables in prototypes 2012-01-05 14:03:09 -08:00
Pieter Noordhuis
307e29ac8c Use address helpers where applicable 2012-01-05 14:00:51 -08:00
Pieter Noordhuis
fbdcfae4a3 More redis_address helpers 2012-01-05 13:45:26 -08:00
Pieter Noordhuis
58737d9f3b Helper functions for redis_address 2012-01-05 13:42:05 -08:00
Pieter Noordhuis
c37187f8ed More use of special return value assertion 2012-01-05 11:53:51 -08:00
Pieter Noordhuis
aa1b9c12b6 Test write timeout on context 2012-01-05 11:40:04 -08:00
Pieter Noordhuis
300b3d32da Retry read/write on EINTR 2012-01-05 09:16:58 -08:00
Pieter Noordhuis
9bb22da68a Writing commands and reading replies for new context 2012-01-04 18:32:04 -08:00
Pieter Noordhuis
a52925d59a Initial stab at a new context structure 2012-01-04 15:34:37 -08:00
Pieter Noordhuis
11ad647ad6 Return address when connecting with getaddrinfo
It is opaque to the caller which address the handle connects to when the
host is resolved by getaddrinfo. Now, it returns a redis_address type
that encapsulates multiple sockaddr_* structs (sockaddr_storage would be
appropriate here, but it appears not to be portable).
2012-01-04 13:59:54 -08:00
Pieter Noordhuis
6d3577ef66 Print test name after running it 2012-01-04 13:59:13 -08:00
Pieter Noordhuis
67cf82240e Require the static library to be up to date 2012-01-04 13:56:00 -08:00
Pieter Noordhuis
92e6324343 Factor out optimization from CFLAGS 2012-01-04 13:54:01 -08:00
Pieter Noordhuis
d9759edb70 Move common Makefile variables to separate file 2012-01-04 13:47:48 -08:00
Pieter Noordhuis
910d46c53d Add function that returns timeout on handle 2012-01-04 12:04:44 -08:00
Pieter Noordhuis
52daeaff70 Private connect function takes a const 2012-01-04 12:03:48 -08:00
Pieter Noordhuis
271cca8d8c Use macro in formatting tests 2012-01-04 10:36:31 -08:00
Pieter Noordhuis
b9e0d8a6e8 Align escapes 2012-01-04 10:33:01 -08:00
Pieter Noordhuis
7e773ee238 Use macro for test definition
This makes sure that tests are declared static, so the compiler
complains when they are not used. Declaring static is easy to forget,
and we want to make sure all tests are run!
2012-01-04 10:31:39 -08:00
Pieter Noordhuis
47f23f0a16 Factor out code run after successful connection 2012-01-04 09:29:40 -08:00
Pieter Noordhuis
07db464b53 Factor protocol formatting code to separate file 2011-10-10 14:11:08 +02:00
Pieter Noordhuis
10da4544a6 Buffered read/write for handle 2011-10-01 11:35:28 +02:00
Pieter Noordhuis
c06801cba9 Test successful connection 2011-10-01 01:14:15 +02:00
Pieter Noordhuis
aacf12c2de Bootstrap redis_handle 2011-10-01 01:03:47 +02:00
Pieter Noordhuis
6ccf65d5c4 Fix compiler warnings 2011-09-30 18:41:36 +02:00
Pieter Noordhuis
74d000ae26 Implement redis_object destroy callback 2011-09-30 18:41:17 +02:00
Pieter Noordhuis
5bd66cae90 Fix warnings in parser tests 2011-09-30 18:24:50 +02:00
Pieter Noordhuis
87d9d48b1b Ignore compiler warning on unused labels 2011-09-30 18:24:27 +02:00
Pieter Noordhuis
54a39608e0 Destroy hook for parser 2011-09-30 18:18:07 +02:00
Pieter Noordhuis
7540faf39d Extra object tests 2011-09-30 17:47:10 +02:00
Pieter Noordhuis
306c97dcd4 Fix build for new tests 2011-09-30 16:16:19 +02:00
Pieter Noordhuis
1375e4008c Extract object code to separate file (+refactor) 2011-09-30 15:37:24 +02:00
Pieter Noordhuis
70fe4177ab Increment array cursor after completing nested object 2011-09-30 15:26:38 +02:00
Pieter Noordhuis
dfc0c796e3 String equality helper 2011-09-30 15:19:13 +02:00
Pieter Noordhuis
77a045ff4d Destroy callback prototype 2011-09-30 15:19:04 +02:00
Pieter Noordhuis
bb2d09113c Move custom asserts to test helper 2011-09-30 14:28:34 +02:00
Pieter Noordhuis
c288dd29b9 Change callback type prefix 2011-09-30 14:25:34 +02:00
Pieter Noordhuis
679b231774 Add destroy callback to parser 2011-09-30 14:25:34 +02:00
Pieter Noordhuis
71f280f065 Whitespace 2011-09-30 14:25:34 +02:00
Pieter Noordhuis
7d260dd77b Drop the _t suffix 2011-09-30 14:25:29 +02:00
Pieter Noordhuis
a866667fd4 Add total bulk/multi bulk size to protocol_t 2011-09-29 12:53:04 +02:00
Pieter Noordhuis
b852caf345 Abbreviate errors http-parser.c style 2011-09-29 11:52:36 +02:00
Pieter Noordhuis
e791703fdd Makefile target for parser tests 2011-09-29 11:51:52 +02:00
Pieter Noordhuis
62a36ab1f9 Fix up includes for parser tests 2011-09-29 11:49:35 +02:00
Pieter Noordhuis
c6bbe8403f Rename file with parser tests 2011-09-29 11:33:24 +02:00
Pieter Noordhuis
a69942ccd7 Style 2011-09-29 11:33:15 +02:00
Pieter Noordhuis
4d3f9aa1b7 Change include protection 2011-07-20 11:48:00 +02:00
Pieter Noordhuis
fdc802349f Fix type for nil objects 2011-07-15 11:42:17 +02:00
Pieter Noordhuis
4234b1cf2c Function to pull root object from parser 2011-07-14 20:18:51 +02:00
Pieter Noordhuis
29617b1a65 Rename occurances of errno (conflict with errno.h) 2011-07-14 17:02:26 +02:00
Pieter Noordhuis
82555dfaad Abort if the parser is in an error state 2011-07-14 14:41:28 +02:00
Pieter Noordhuis
32d86ef979 Align escapes 2011-07-14 14:22:10 +02:00
Pieter Noordhuis
d9c056721f Use custom asserts to have immediate introspection 2011-07-12 17:13:34 +02:00
Pieter Noordhuis
b550b8c800 More tests for status and error messages 2011-07-12 16:34:31 +02:00
Pieter Noordhuis
628a961f86 Make test more exhaustive 2011-07-12 16:29:55 +02:00
Pieter Noordhuis
6e98f37390 Debugging output in tests 2011-07-12 16:29:38 +02:00
Pieter Noordhuis
9adb3aeda7 Apply segmented test to 64-bit extremes 2011-07-12 16:17:56 +02:00
Pieter Noordhuis
ee443ce59d Use 3 slices in the segmented data test 2011-07-12 16:15:25 +02:00
Pieter Noordhuis
45ed2729ee Change how parse outcome is compared 2011-07-12 16:01:49 +02:00
Pieter Noordhuis
87055888be Use macro 2011-07-12 12:56:01 +02:00
Pieter Noordhuis
b50af6c539 Jump directly to finalize from line state 2011-07-12 12:55:39 +02:00
Pieter Noordhuis
64288d2cc4 Add cursor field to protocol_t 2011-07-12 12:55:18 +02:00
Pieter Noordhuis
2fc0af5479 Base *_T constants on current constants for compat 2011-07-12 11:53:35 +02:00
Pieter Noordhuis
e43597aee8 Be more UNIXy (0 == success) 2011-07-12 10:34:40 +02:00
Pieter Noordhuis
21858350e8 More verbose type name 2011-07-12 10:33:45 +02:00
Pieter Noordhuis
095b376573 Use different parser in char-by-char tests
This fixes the test always succeeding because the redis_protocol_t in
the result is identical to the redis_protocol_t that is used as
reference when the same parser is used (lame bug).
2011-07-11 18:18:38 +02:00
Pieter Noordhuis
ec934e92d8 Parse status and error messages 2011-07-11 18:18:08 +02:00
Pieter Noordhuis
3c378b8422 Basic error facility 2011-07-07 14:13:36 +02:00
Pieter Noordhuis
a25cd930dc Define state strings only in DEBUG mode 2011-07-07 13:01:33 +02:00
Pieter Noordhuis
df7348053b Prefix 2011-07-07 12:59:54 +02:00
Pieter Noordhuis
ec7eaed9f2 No longer store integer sign in struct 2011-07-07 11:37:12 +02:00
Pieter Noordhuis
fd5898290f Use macro 2011-07-07 11:05:18 +02:00
Pieter Noordhuis
3672c7465f States for positive/negative integers
This enables the parser to bail out on the first character that causes
an overflow instead of checking for an overflow on the LF character.
Doing this without separate states requires too much branching in the
main loop that consumes integers (positive/negative overflow boundaries
are different).
2011-07-07 11:02:32 +02:00
Pieter Noordhuis
80acfd9d24 Minor structural changes for parser tests 2011-07-07 10:11:12 +02:00
Pieter Noordhuis
ef0864c50f Reorder struct 2011-07-05 21:24:30 +02:00
Pieter Noordhuis
09346dccf5 Fix preprocessor warnings 2011-07-05 21:23:43 +02:00
Pieter Noordhuis
0426ddade3 Print struct size 2011-07-05 20:50:32 +02:00
Pieter Noordhuis
b127f31e12 Rename macros 2011-07-05 17:24:24 +02:00
Pieter Noordhuis
6134606604 The "cur" pointer is valid in the first iteration 2011-07-05 17:18:07 +02:00
Pieter Noordhuis
e5f1193101 Only use char when really necessary 2011-07-05 17:11:26 +02:00
Pieter Noordhuis
bf14e87a10 State definition using macro 2011-07-05 17:07:32 +02:00
Pieter Noordhuis
b6e6624aa8 Change variable names in bulk state 2011-07-05 16:51:23 +02:00
Pieter Noordhuis
e7e12adef1 Indentation 2011-07-05 10:41:16 +02:00
Pieter Noordhuis
bd96d1f9fe Style and comments 2011-07-05 10:37:10 +02:00
Pieter Noordhuis
b42a144c1e More parser tests 2011-07-05 10:30:21 +02:00
Pieter Noordhuis
5a46b31173 Fill log with non-zero to be able to test for 0 2011-07-05 10:29:40 +02:00
Pieter Noordhuis
acf174fba7 Fix tests 2011-07-05 10:28:02 +02:00
Pieter Noordhuis
4088a8f918 Reintroduce positive sign 2011-07-05 10:24:15 +02:00
Pieter Noordhuis
5f1212ed6e Reorder jumps in s_integer_start 2011-07-05 10:21:56 +02:00
Pieter Noordhuis
422bd45393 Fix parser for integer == 0 2011-07-05 10:18:35 +02:00
Pieter Noordhuis
572a96744d Move overflow check to s_integer_lf 2011-07-05 10:12:54 +02:00
Pieter Noordhuis
73b6930e07 Rename integer states 2011-07-05 10:09:59 +02:00
Pieter Noordhuis
bb95fb448d Ignore '+' character 2011-07-05 10:06:11 +02:00
Pieter Noordhuis
112019a08d Print values on assertion failure 2011-07-05 09:38:37 +02:00
Pieter Noordhuis
03a860884b Comment fix 2011-07-02 15:01:46 +02:00
Pieter Noordhuis
032ed29a1f Inner while loop is no longer useful 2011-07-02 14:57:08 +02:00
Pieter Noordhuis
37c16d6d60 Extract separate state for the CR after a bulk 2011-07-02 14:42:33 +02:00
Pieter Noordhuis
9b4666788a Move per-type actions to if-blocks 2011-07-02 14:15:07 +02:00
Pieter Noordhuis
b919612b8b Cut down on conditional jumps 2011-07-02 13:59:57 +02:00
Pieter Noordhuis
dcb783abf8 Move temporary integer to the stack 2011-07-02 09:51:45 +02:00
Pieter Noordhuis
96a5c166cb Fix warning 2011-06-30 17:56:16 +02:00
Pieter Noordhuis
4783ee2733 Integer overflow protection 2011-06-30 17:53:13 +02:00
Pieter Noordhuis
d128380b93 Basic integer type tests 2011-06-30 17:18:57 +02:00
Pieter Noordhuis
4ae503d1a9 Initial tests for new parser 2011-06-30 17:08:58 +02:00
Pieter Noordhuis
e7d8b0c78a Aggregate protocol length going up the stack 2011-06-30 17:08:16 +02:00
Pieter Noordhuis
624ba36502 Store protocol offset 2011-06-30 17:07:16 +02:00
Pieter Noordhuis
5d94b82adf Decrease number of remaining protocol_t's to parse 2011-06-30 16:52:11 +02:00
Pieter Noordhuis
b9427f4110 Pass pointer to redis_protocol_t when message was read 2011-06-30 16:51:09 +02:00
Pieter Noordhuis
d96c4c9e8b Change prototype 2011-06-30 16:49:40 +02:00
Pieter Noordhuis
d64575b236 Stupid macro error 2011-06-30 16:49:09 +02:00
Pieter Noordhuis
cbe59756f8 First stab at a non-buffering reentrant parser 2011-06-30 14:43:50 +02:00
41 changed files with 5304 additions and 67 deletions

8
.gitignore vendored
View File

@ -1,6 +1,6 @@
/hiredis-test
/hiredis-example*
/*.o
/*.so
/*.dylib
/*.a
*.o
*.so
*.dylib
*.a

View File

@ -3,54 +3,13 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
OBJ=net.o hiredis.o sds.o async.o
include ./Makefile.common
OBJ=net.o hiredis.o sds.o async.o parser.o object.o handle.o format.o context.o address.o request.o fd.o
BINS=hiredis-example hiredis-test
LIBNAME=libhiredis
HIREDIS_MAJOR=0
HIREDIS_MINOR=10
# Fallback to gcc when $CC is not in $PATH.
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
DEBUG?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG)
REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so
STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
# Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
INSTALL= cp -r
endif
ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
endif
all: $(DYLIBNAME) $(BINS)
# Deps (use make dep to generate this)
net.o: net.c fmacros.h net.h hiredis.h
async.o: async.c async.h hiredis.h sds.h dict.c dict.h
example.o: example.c hiredis.h
hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h
sds.o: sds.c sds.h
test.o: test.c hiredis.h
$(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) $(OBJ)
@ -95,13 +54,15 @@ check: hiredis-test
kill `cat /tmp/hiredis-test-redis.pid`
.c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
$(CC) -std=c99 -pedantic -c $(OPTIMIZATION) $(REAL_CFLAGS) $<
clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov
rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov test/*.o
-include ./Makefile.dep
dep:
$(CC) -MM *.c
$(CC) -MM *.c > Makefile.dep
# Installation related variables and target
PREFIX?=/usr/local

35
Makefile.common Normal file
View File

@ -0,0 +1,35 @@
LIBNAME=libhiredis
HIREDIS_MAJOR=0
HIREDIS_MINOR=10
# Fallback to gcc when $CC is not in $PATH.
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-unused-label
DEBUG?= -g -ggdb
REAL_CFLAGS=-fPIC $(CFLAGS) $(WARNINGS) $(DEBUG)
REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so
STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
# Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
INSTALL= cp -r
endif
ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
endif

View File

@ -1,16 +0,0 @@
#ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H
#if !defined(_BSD_SOURCE)
#define _BSD_SOURCE
#endif
#if defined(__sun__)
#define _POSIX_C_SOURCE 200112L
#elif defined(__linux__)
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE
#endif
#endif

39
src/Makefile Normal file
View File

@ -0,0 +1,39 @@
# Hiredis Makefile
# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
include ./Makefile.common
OBJ= \
address.o \
context.o \
fd.o \
format.o \
handle.o \
object.o \
parser.o \
request.o \
sds.o \
all: $(DYLIBNAME)
$(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) $(OBJ)
$(STLIBNAME): $(OBJ)
$(STLIB_MAKE_CMD) $(OBJ)
dynamic: $(DYLIBNAME)
static: $(STLIBNAME)
.c.o:
$(HIREDIS_CC) -c $<
clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) *.o
-include ./Makefile.dep
dep:
$(HIREDIS_CC) -MM *.c > Makefile.dep

42
src/Makefile.common Normal file
View File

@ -0,0 +1,42 @@
LIBNAME= libhiredis
HIREDIS_MAJOR= 0
HIREDIS_MINOR= 11
DYLIBSUFFIX= so
STLIBSUFFIX= a
DYLIB_MINOR_NAME= $(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
DYLIB_MAJOR_NAME= $(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME= $(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD= $(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(FINAL_LDFLAGS)
STLIBNAME= $(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD= ar rcs $(STLIBNAME)
# Default settings
STD= -std=c99 -pedantic -D_POSIX_C_SOURCE=200112L
WARN= -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-unused-label
OPT= -O3
FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) -fPIC $(CFLAGS) $(HIREDIS_CFLAGS)
FINAL_LDFLAGS= $(DEBUG) $(LDFLAGS) $(HIREDIS_LDFLAGS)
FINAL_LIBS= $(HIREDIS_LIBS)
DEBUG= -g -ggdb
HIREDIS_CC= $(CC) $(FINAL_CFLAGS)
HIREDIS_LD= $(CC) $(FINAL_LDFLAGS)
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
# SunOS overrides
ifeq ($(uname_S),SunOS)
HIREDIS_CFLAGS= -D__EXTENSIONS__ -D_XPG6
HIREDIS_LDFLAGS=
HIREDIS_LIBS= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(FINAL_LDFLAGS) $(FINAL_LIBS)
INSTALL= cp -r
endif
# Darwin overrides
ifeq ($(uname_S),Darwin)
DYLIBSUFFIX= dylib
DYLIB_MINOR_NAME= $(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
DYLIB_MAJOR_NAME= $(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD= $(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(FINAL_LDFLAGS) $(FINAL_LIBS)
endif

11
src/Makefile.dep Normal file
View File

@ -0,0 +1,11 @@
address.o: address.c address.h
context.o: context.c context.h handle.h address.h common.h parser.h \
format.h object.h
fd.o: fd.c fd.h address.h common.h
format.o: format.c format.h sds.h
handle.o: handle.c fd.h address.h common.h handle.h parser.h sds.h
object.o: object.c object.h parser.h
parser.o: parser.c parser.h
request.o: request.c handle.h address.h common.h parser.h object.h \
request.h ngx-queue.h
sds.o: sds.c sds.h

65
src/address.c Normal file
View File

@ -0,0 +1,65 @@
#include <arpa/inet.h>
#include <assert.h>
#include <string.h>
#include "address.h"
redis_address redis_address_from_in(struct sockaddr_in sa) {
redis_address addr;
memset(&addr, 0, sizeof(addr));
addr.sa_family = sa.sin_family;
addr.sa_addrlen = sizeof(sa);
addr.sa_addr.in = sa;
return addr;
}
redis_address redis_address_from_in6(struct sockaddr_in6 sa) {
redis_address addr;
memset(&addr, 0, sizeof(addr));
addr.sa_family = sa.sin6_family;
addr.sa_addrlen = sizeof(sa);
addr.sa_addr.in6 = sa;
return addr;
}
redis_address redis_address_from_un(struct sockaddr_un sa) {
redis_address addr;
memset(&addr, 0, sizeof(addr));
addr.sa_family = sa.sun_family;
addr.sa_addrlen = sizeof(sa);
addr.sa_addr.un = sa;
return addr;
}
redis_address redis_address_in(const char *ip, int port) {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
assert(inet_pton(AF_INET, ip, &sa.sin_addr) == 1);
return redis_address_from_in(sa);
}
redis_address redis_address_in6(const char *ip, int port) {
struct sockaddr_in6 sa;
memset(&sa, 0, sizeof(sa));
sa.sin6_family = AF_INET6;
sa.sin6_port = htons(port);
assert(inet_pton(AF_INET6, ip, &sa.sin6_addr) == 1);
return redis_address_from_in6(sa);
}
redis_address redis_address_un(const char *path) {
struct sockaddr_un sa;
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
strncpy((char*)&sa.sun_path, path, sizeof(sa.sun_path));
sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
return redis_address_from_un(sa);
}

27
src/address.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef HIREDIS_ADDRESS_H
#define HIREDIS_ADDRESS_H 1
#include <netinet/in.h>
#include <sys/un.h>
typedef struct redis_address_s redis_address;
struct redis_address_s {
int sa_family;
socklen_t sa_addrlen;
union {
struct sockaddr addr;
struct sockaddr_in in;
struct sockaddr_in6 in6;
struct sockaddr_un un;
} sa_addr;
};
redis_address redis_address_from_in(struct sockaddr_in sa);
redis_address redis_address_from_in6(struct sockaddr_in6 sa);
redis_address redis_address_from_un(struct sockaddr_un sa);
redis_address redis_address_in(const char *ip, int port);
redis_address redis_address_in6(const char *ip, int port);
redis_address redis_address_un(const char *path);
#endif

10
src/common.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef HIREDIS_COMMON_H
#define HIREDIS_COMMON_H 1
#define REDIS_OK 0
#define REDIS_ESYS -1
#define REDIS_EGAI -2
#define REDIS_EPARSER -3
#define REDIS_EEOF -4
#endif

281
src/context.c Normal file
View File

@ -0,0 +1,281 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "context.h"
#include "format.h"
#include "object.h"
static void redis__clear_address(redis_context *ctx) {
memset(&ctx->address, 0, sizeof(ctx->address));
}
int redis_context_init(redis_context *ctx) {
int rv;
rv = redis_handle_init(&ctx->handle);
if (rv != REDIS_OK) {
return rv;
}
/* Pull default timeout from handle */
ctx->timeout = redis_handle_get_timeout(&ctx->handle);
/* Reinitialize parser with bundled object functions */
redis_parser_init(&ctx->handle.parser, &redis_object_parser_callbacks);
/* Clear address field */
redis__clear_address(ctx);
return REDIS_OK;
}
int redis_context_destroy(redis_context *ctx) {
int rv;
rv = redis_handle_destroy(&ctx->handle);
return rv;
}
int redis_context_set_timeout(redis_context *ctx, unsigned long timeout) {
int rv;
rv = redis_handle_set_timeout(&ctx->handle, timeout);
if (rv == REDIS_OK) {
ctx->timeout = timeout;
}
return rv;
}
unsigned long redis_context_get_timeout(redis_context *ctx) {
return ctx->timeout;
}
static int redis__connect(redis_context *ctx) {
int rv;
rv = redis_handle_connect_address(&ctx->handle, ctx->address);
if (rv != REDIS_OK) {
return rv;
}
rv = redis_handle_wait_connected(&ctx->handle);
if (rv != REDIS_OK) {
return rv;
}
return REDIS_OK;
}
int redis_context_connect_address(redis_context *ctx, const redis_address addr) {
int rv;
ctx->address = addr;
rv = redis__connect(ctx);
if (rv != REDIS_OK) {
redis__clear_address(ctx);
return rv;
}
return REDIS_OK;
}
int redis_context_connect_in(redis_context *ctx, struct sockaddr_in addr) {
return redis_context_connect_address(ctx, redis_address_from_in(addr));
}
int redis_context_connect_in6(redis_context *ctx, struct sockaddr_in6 addr) {
return redis_context_connect_address(ctx, redis_address_from_in6(addr));
}
int redis_context_connect_un(redis_context *ctx, struct sockaddr_un addr) {
return redis_context_connect_address(ctx, redis_address_from_un(addr));
}
int redis_context_connect_gai(redis_context *ctx, const char *host, int port) {
redis_address address;
int rv;
rv = redis_handle_connect_gai(&ctx->handle, AF_INET, host, port, &address);
if (rv != REDIS_OK) {
return rv;
}
rv = redis_handle_wait_connected(&ctx->handle);
if (rv != REDIS_OK) {
return rv;
}
/* Store address that getaddrinfo resolved and we are now connected to */
ctx->address = address;
return REDIS_OK;
}
int redis_context_flush(redis_context *ctx) {
int rv;
int drained = 0;
while (!drained) {
rv = redis_handle_write_from_buffer(&ctx->handle, &drained);
if (rv == REDIS_OK) {
continue;
}
/* Wait for the socket to become writable on EAGAIN */
if (rv == REDIS_ESYS && errno == EAGAIN) {
rv = redis_handle_wait_writable(&ctx->handle);
if (rv != REDIS_OK) {
return rv;
}
continue;
}
return rv;
}
return REDIS_OK;
}
int redis_context_read(redis_context *ctx, redis_protocol **reply) {
int rv;
redis_protocol *aux = NULL;
rv = redis_handle_read_from_buffer(&ctx->handle, &aux);
if (rv != REDIS_OK) {
return rv;
}
/* No error, no reply: make sure write buffers are flushed */
if (aux == NULL) {
rv = redis_context_flush(ctx);
if (rv != REDIS_OK) {
return rv;
}
}
while (aux == NULL) {
rv = redis_handle_wait_readable(&ctx->handle);
if (rv != REDIS_OK) {
return rv;
}
rv = redis_handle_read_to_buffer(&ctx->handle);
if (rv != REDIS_OK) {
return rv;
}
rv = redis_handle_read_from_buffer(&ctx->handle, &aux);
if (rv != REDIS_OK) {
return rv;
}
}
assert(aux != NULL);
*reply = aux;
return REDIS_OK;
}
int redis_context_write_vcommand(redis_context *ctx,
const char *format,
va_list ap)
{
char *buf;
int len;
int rv;
len = redis_format_vcommand(&buf, format, ap);
if (len < 0) {
errno = ENOMEM;
return REDIS_ESYS;
}
/* Write command to output buffer in handle */
rv = redis_handle_write_to_buffer(&ctx->handle, buf, len);
free(buf);
return rv;
}
int redis_context_write_command(redis_context *ctx,
const char *format,
...)
{
va_list ap;
int rv;
va_start(ap, format);
rv = redis_context_write_vcommand(ctx, format, ap);
va_end(ap);
return rv;
}
int redis_context_write_command_argv(redis_context *ctx,
int argc,
const char **argv,
const size_t *argvlen)
{
char *buf;
int len;
int rv;
len = redis_format_command_argv(&buf, argc, argv, argvlen);
if (len < 0) {
errno = ENOMEM;
return REDIS_ESYS;
}
/* Write command to output buffer in handle */
rv = redis_handle_write_to_buffer(&ctx->handle, buf, len);
free(buf);
return rv;
}
int redis_context_call_vcommand(redis_context *ctx,
redis_protocol **reply,
const char *format,
va_list ap)
{
int rv;
rv = redis_context_write_vcommand(ctx, format, ap);
if (rv != REDIS_OK) {
return rv;
}
return redis_context_read(ctx, reply);
}
int redis_context_call_command(redis_context *ctx,
redis_protocol **reply,
const char *format,
...)
{
va_list ap;
int rv;
va_start(ap, format);
rv = redis_context_call_vcommand(ctx, reply, format, ap);
va_end(ap);
return rv;
}
int redis_context_call_command_argv(redis_context *ctx,
redis_protocol **reply,
int argc,
const char **argv,
const size_t *argvlen)
{
int rv;
rv = redis_context_write_command_argv(ctx, argc, argv, argvlen);
if (rv != REDIS_OK) {
return rv;
}
return redis_context_read(ctx, reply);
}

57
src/context.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef HIREDIS_CONTEXT_H
#define HIREDIS_CONTEXT_H 1
#include <stdarg.h>
#include "handle.h"
typedef struct redis_context_s redis_context;
struct redis_context_s {
redis_handle handle;
unsigned long timeout;
/* private: used to reconnect */
redis_address address;
};
int redis_context_init(redis_context *ctx);
int redis_context_destroy(redis_context *ctx);
int redis_context_set_timeout(redis_context *ctx, unsigned long us);
unsigned long redis_handle_get_timeout(redis_handle *ctx);
int redis_context_connect_address(redis_context *ctx, const redis_address addr);
int redis_context_connect_in(redis_context *ctx, struct sockaddr_in sa);
int redis_context_connect_in6(redis_context *ctx, struct sockaddr_in6 sa);
int redis_context_connect_un(redis_context *ctx, struct sockaddr_un sa);
int redis_context_connect_gai(redis_context *ctx, const char *addr, int port);
int redis_context_flush(redis_context *ctx);
int redis_context_read(redis_context *ctx, redis_protocol **reply);
int redis_context_write_vcommand(redis_context *ctx,
const char *format,
va_list ap);
int redis_context_write_command(redis_context *ctx,
const char *format,
...);
int redis_context_write_command_argv(redis_context *ctx,
int argc,
const char **argv,
const size_t *argvlen);
int redis_context_call_vcommand(redis_context *ctx,
redis_protocol **reply,
const char *format,
va_list ap);
int redis_context_call_command(redis_context *ctx,
redis_protocol **reply,
const char *format,
...);
int redis_context_call_command_argv(redis_context *ctx,
redis_protocol **reply,
int argc,
const char **argv,
const size_t *argvlen);
#endif

169
src/fd.c Normal file
View File

@ -0,0 +1,169 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "fd.h"
int redis_fd_error(int fd) {
int err = 0;
socklen_t errlen = sizeof(err);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
return REDIS_ESYS;
}
return err;
}
int redis_fd_read(int fildes, void *buf, size_t nbyte) {
int nread;
do {
nread = read(fildes, buf, nbyte);
} while (nread == -1 && errno == EINTR);
if (nread == -1) {
return REDIS_ESYS;
}
if (nread == 0) {
return REDIS_EEOF;
}
return nread;
}
int redis_fd_write(int fildes, const void *buf, size_t nbyte) {
int nwritten;
do {
nwritten = write(fildes, buf, nbyte);
} while (nwritten == -1 && errno == EINTR);
if (nwritten == -1) {
return REDIS_ESYS;
}
return nwritten;
}
static int redis__nonblock(int fd, int nonblock) {
int flags;
if ((flags = fcntl(fd, F_GETFL)) == -1) {
return REDIS_ESYS;
}
if (nonblock) {
flags |= O_NONBLOCK;
} else {
flags &= ~O_NONBLOCK;
}
if (fcntl(fd, F_SETFL, flags) == -1) {
return REDIS_ESYS;
}
return REDIS_OK;
}
static int redis__connect(int family, const struct sockaddr *addr, socklen_t addrlen) {
int fd, rv;
int on = 1;
fd = socket(family, SOCK_STREAM, 0);
if (fd == -1) {
goto error;
}
if (family == AF_INET || family == AF_INET6) {
rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (rv == -1) {
goto error;
}
}
/* The socket needs to be non blocking to be able to timeout connect(2). */
rv = redis__nonblock(fd, 1);
if (rv != REDIS_OK) {
goto error;
}
rv = connect(fd, addr, addrlen);
if (rv == -1 && errno != EINPROGRESS) {
goto error;
}
rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
if (rv == -1) {
goto error;
}
return fd;
error:
close(fd);
return REDIS_ESYS;
}
int redis_fd_connect_address(const redis_address addr) {
return redis__connect(addr.sa_family, &addr.sa_addr.addr, addr.sa_addrlen);
}
int redis_fd_connect_gai(int family,
const char *addr,
int port,
redis_address *_addr)
{
char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *p;
int rv, fd;
snprintf(_port, 6, "%d", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo(addr, _port, &hints, &servinfo)) != 0) {
errno = rv;
return REDIS_EGAI;
}
/* Expect at least one record. */
assert(servinfo != NULL);
for (p = servinfo; p != NULL; p = p->ai_next) {
fd = redis__connect(p->ai_family, p->ai_addr, p->ai_addrlen);
if (fd == -1) {
if (errno == EHOSTUNREACH) {
/* AF_INET6 record on a machine without IPv6 support.
* See c4ed06d9 for more information. */
continue;
}
goto error;
}
/* Pass address we connect to back to caller */
if (_addr != NULL) {
memset(_addr, 0, sizeof(*_addr));
memcpy(&_addr->sa_addr, p->ai_addr, p->ai_addrlen);
_addr->sa_family = p->ai_family;
_addr->sa_addrlen = p->ai_addrlen;
}
freeaddrinfo(servinfo);
return fd;
}
error:
freeaddrinfo(servinfo);
return REDIS_ESYS;
}

13
src/fd.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef HIREDIS_FD_H
#define HIREDIS_FD_H 1
#include "address.h"
#include "common.h"
int redis_fd_error(int fd);
int redis_fd_read(int fildes, void *buf, size_t nbyte);
int redis_fd_write(int fildes, const void *buf, size_t nbyte);
int redis_fd_connect_address(const redis_address addr);
int redis_fd_connect_gai(int family, const char *host, int port, redis_address *addr);
#endif

315
src/format.c Normal file
View File

@ -0,0 +1,315 @@
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "format.h"
#include "sds.h"
/* Number of bytes to represent specified integer as string */
static int intlen(int i) {
int len = 0;
if (i < 0) {
len++;
i = -i;
}
do {
len++;
i /= 10;
} while(i);
return len;
}
/* Protocol length of bulk with specified length */
static size_t bulklen(size_t len) {
return 1+intlen(len)+2+len+2;
}
int redis_format_vcommand(char **target, const char *format, va_list ap) {
const char *c = format;
char *cmd = NULL; /* final command */
int pos; /* position in final command */
sds curarg, newarg; /* current argument */
int touched = 0; /* was the current argument touched? */
char **curargv = NULL, **newargv = NULL;
int argc = 0;
int totlen = 0;
int j;
/* Abort if there is not target to set */
if (target == NULL)
return -1;
/* Build the command string accordingly to protocol */
curarg = sdsempty();
if (curarg == NULL)
return -1;
while(*c != '\0') {
if (*c != '%' || c[1] == '\0') {
if (*c == ' ') {
if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto err;
curargv = newargv;
curargv[argc++] = curarg;
totlen += bulklen(sdslen(curarg));
/* curarg is put in argv so it can be overwritten. */
curarg = sdsempty();
if (curarg == NULL) goto err;
touched = 0;
}
} else {
newarg = sdscatlen(curarg,c,1);
if (newarg == NULL) goto err;
curarg = newarg;
touched = 1;
}
} else {
char *arg;
size_t size;
/* Set newarg so it can be checked even if it is not touched. */
newarg = curarg;
switch(c[1]) {
case 's':
arg = va_arg(ap,char*);
size = strlen(arg);
if (size > 0)
newarg = sdscatlen(curarg,arg,size);
break;
case 'b':
arg = va_arg(ap,char*);
size = va_arg(ap,size_t);
if (size > 0)
newarg = sdscatlen(curarg,arg,size);
break;
case '%':
newarg = sdscat(curarg,"%");
break;
default:
/* Try to detect printf format */
{
static const char int_fmts[] = "diouxX";
static const char double_fmts[] = "eEfFgGaA";
char _format[16];
const char *_p = c+1;
size_t _l = 0;
va_list _cpy;
/* Flags */
if (*_p != '\0' && *_p == '#') _p++;
if (*_p != '\0' && *_p == '0') _p++;
if (*_p != '\0' && *_p == '-') _p++;
if (*_p != '\0' && *_p == ' ') _p++;
if (*_p != '\0' && *_p == '+') _p++;
/* Field width */
while (*_p != '\0' && isdigit(*_p)) _p++;
/* Precision */
if (*_p == '.') {
_p++;
while (*_p != '\0' && isdigit(*_p)) _p++;
}
/* Copy va_list before consuming with va_arg */
va_copy(_cpy,ap);
/* Integer conversion (without modifiers) */
if (strchr(int_fmts, *_p) != NULL) {
va_arg(ap,int);
goto fmt_valid;
}
/* Double conversion (without modifiers) */
if (strchr(double_fmts, *_p) != NULL) {
va_arg(ap,double);
goto fmt_valid;
}
/* Size: char */
if (_p[0] == 'h' && _p[1] == 'h') {
_p += 2;
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
va_arg(ap,int); /* char gets promoted to int */
goto fmt_valid;
}
goto fmt_invalid;
}
/* Size: short */
if (_p[0] == 'h') {
_p += 1;
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
va_arg(ap,int); /* short gets promoted to int */
goto fmt_valid;
}
goto fmt_invalid;
}
/* Size: long long */
if (_p[0] == 'l' && _p[1] == 'l') {
_p += 2;
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
va_arg(ap,long long);
goto fmt_valid;
}
goto fmt_invalid;
}
/* Size: long */
if (_p[0] == 'l') {
_p += 1;
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
va_arg(ap,long);
goto fmt_valid;
}
goto fmt_invalid;
}
fmt_invalid:
va_end(_cpy);
goto err;
fmt_valid:
_l = (_p+1)-c;
if (_l < sizeof(_format)-2) {
memcpy(_format,c,_l);
_format[_l] = '\0';
newarg = sdscatvprintf(curarg,_format,_cpy);
/* Update current position (note: outer blocks
* increment c twice so compensate here) */
c = _p-1;
}
va_end(_cpy);
break;
}
}
if (newarg == NULL) goto err;
curarg = newarg;
touched = 1;
c++;
}
c++;
}
/* Add the last argument if needed */
if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto err;
curargv = newargv;
curargv[argc++] = curarg;
totlen += bulklen(sdslen(curarg));
} else {
sdsfree(curarg);
}
/* Clear curarg because it was put in curargv or was free'd. */
curarg = NULL;
/* Add bytes needed to hold multi bulk count */
totlen += 1+intlen(argc)+2;
/* Build the command at protocol level */
cmd = malloc(totlen+1);
if (cmd == NULL) goto err;
pos = sprintf(cmd,"*%d\r\n",argc);
for (j = 0; j < argc; j++) {
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
pos += sdslen(curargv[j]);
sdsfree(curargv[j]);
cmd[pos++] = '\r';
cmd[pos++] = '\n';
}
assert(pos == totlen);
cmd[pos] = '\0';
free(curargv);
*target = cmd;
return totlen;
err:
while(argc--)
sdsfree(curargv[argc]);
free(curargv);
if (curarg != NULL)
sdsfree(curarg);
/* No need to check cmd since it is the last statement that can fail,
* but do it anyway to be as defensive as possible. */
if (cmd != NULL)
free(cmd);
return -1;
}
/* Format a command according to the Redis protocol. This function
* takes a format similar to printf:
*
* %s represents a C null terminated string you want to interpolate
* %b represents a binary safe string
*
* When using %b you need to provide both the pointer to the string
* and the length in bytes. Examples:
*
* len = redisFormatCommand(target, "GET %s", mykey);
* len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
*/
int redis_format_command(char **target, const char *format, ...) {
va_list ap;
int len;
va_start(ap,format);
len = redis_format_vcommand(target,format,ap);
va_end(ap);
return len;
}
/* Format a command according to the Redis protocol. This function takes the
* number of arguments, an array with arguments and an array with their
* lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths.
*/
int redis_format_command_argv(char **target, int argc, const char **argv, const size_t *argvlen) {
char *cmd = NULL; /* final command */
int pos; /* position in final command */
size_t len;
int totlen, j;
/* Calculate number of bytes needed for the command */
totlen = 1+intlen(argc)+2;
for (j = 0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
totlen += bulklen(len);
}
/* Build the command at protocol level */
cmd = malloc(totlen+1);
if (cmd == NULL)
return -1;
pos = sprintf(cmd,"*%d\r\n",argc);
for (j = 0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
pos += sprintf(cmd+pos,"$%zu\r\n",len);
memcpy(cmd+pos,argv[j],len);
pos += len;
cmd[pos++] = '\r';
cmd[pos++] = '\n';
}
assert(pos == totlen);
cmd[pos] = '\0';
*target = cmd;
return totlen;
}

10
src/format.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef HIREDIS_FORMAT_H
#define HIREDIS_FORMAT_H 1
#include <stdarg.h>
int redis_format_vcommand(char **target, const char *format, va_list ap);
int redis_format_command(char **target, const char *format, ...);
int redis_format_command_argv(char **target, int argc, const char **argv, const size_t *argvlen);
#endif

288
src/handle.c Normal file
View File

@ -0,0 +1,288 @@
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "fd.h"
#include "handle.h"
#include "sds.h"
#define REDIS__READABLE 1
#define REDIS__WRITABLE 2
int redis_handle_init(redis_handle *h) {
h->fd = -1;
h->timeout.tv_sec = 5;
h->timeout.tv_usec = 0;
redis_parser_init(&h->parser, NULL);
h->wbuf = NULL;
h->rbuf = NULL;
return REDIS_OK;
}
/* Associate timeout with handle. Only used in redis_handle_wait_* calls. */
int redis_handle_set_timeout(redis_handle *h, unsigned long us) {
struct timeval to;
to.tv_sec = us / 1000000;
to.tv_usec = us - (1000000 * to.tv_sec);
h->timeout = to;
return REDIS_OK;
}
unsigned long redis_handle_get_timeout(redis_handle *h) {
return h->timeout.tv_sec * 1000000 + h->timeout.tv_usec;
}
int redis_handle_close(redis_handle *h) {
if (h->fd >= 0) {
close(h->fd);
h->fd = -1;
}
if (h->wbuf) {
sdsfree(h->wbuf);
h->wbuf = NULL;
}
if (h->rbuf) {
sdsfree(h->rbuf);
h->rbuf = NULL;
}
return REDIS_OK;
}
int redis_handle_destroy(redis_handle *h) {
redis_handle_close(h);
redis_parser_destroy(&h->parser);
return REDIS_OK;
}
static int redis__finish_connect(redis_handle *h, int fd) {
h->fd = fd;
h->wbuf = sdsempty();
h->rbuf = sdsempty();
return REDIS_OK;
}
int redis_handle_connect_address(redis_handle *h, const redis_address addr) {
int fd;
if (h->fd >= 0) {
errno = EALREADY;
return REDIS_ESYS;
}
fd = redis_fd_connect_address(addr);
if (fd < 0) {
return fd;
}
return redis__finish_connect(h, fd);
}
int redis_handle_connect_in(redis_handle *h, struct sockaddr_in sa) {
return redis_handle_connect_address(h, redis_address_from_in(sa));
}
int redis_handle_connect_in6(redis_handle *h, struct sockaddr_in6 sa) {
return redis_handle_connect_address(h, redis_address_from_in6(sa));
}
int redis_handle_connect_un(redis_handle *h, struct sockaddr_un sa) {
return redis_handle_connect_address(h, redis_address_from_un(sa));
}
int redis_handle_connect_gai(redis_handle *h,
int family,
const char *addr,
int port,
redis_address *_addr) {
int fd;
if (h->fd >= 0) {
errno = EALREADY;
return REDIS_ESYS;
}
fd = redis_fd_connect_gai(family, addr, port, _addr);
if (fd < 0) {
return fd;
}
return redis__finish_connect(h, fd);
}
static int redis__poll(int mode, int fd, struct timeval timeout) {
struct pollfd pfd;
int msec;
int rv;
pfd.fd = fd;
pfd.events = 0;
pfd.revents = 0;
if (mode & REDIS__READABLE) {
pfd.events |= POLLIN;
}
if (mode & REDIS__WRITABLE) {
pfd.events |= POLLOUT;
}
msec = (timeout.tv_sec * 1000) + ((timeout.tv_usec + 999) / 1000);
if (msec < 0) {
msec = 0;
}
rv = poll(&pfd, 1, msec);
if (rv == -1) {
return REDIS_ESYS;
}
/* No events means poll(2) timed out */
if (rv == 0) {
errno = ETIMEDOUT;
return REDIS_ESYS;
}
/* POLLERR is always bad */
if (pfd.revents & POLLERR) {
goto error;
}
/* POLLHUP is only bad when interested in POLLOUT.
* When interested in POLLIN, reads can continue until EOF. */
if ((pfd.revents & POLLHUP) && (pfd.events & POLLOUT)) {
goto error;
}
return REDIS_OK;
error:
rv = redis_fd_error(fd);
if (rv < 0) {
return rv;
}
/* We got POLLERR so the socket error MUST be non-zero */
assert(rv && "Expected socket error");
/* Act as if the socket error occured */
errno = rv;
return REDIS_ESYS;
}
static int redis__wait(redis_handle *h, int mode) {
int rv;
if (h->fd < 0) {
errno = EINVAL;
return REDIS_ESYS;
}
rv = redis__poll(mode, h->fd, h->timeout);
if (rv < 0) {
return REDIS_ESYS;
}
return REDIS_OK;
}
int redis_handle_wait_connected(redis_handle *h) {
return redis__wait(h, REDIS__WRITABLE);
}
int redis_handle_wait_readable(redis_handle *h) {
return redis__wait(h, REDIS__READABLE);
}
int redis_handle_wait_writable(redis_handle *h) {
return redis__wait(h, REDIS__WRITABLE);
}
int redis_handle_write_from_buffer(redis_handle *h, int *drained) {
int nwritten;
if (h->fd < 0) {
errno = EINVAL;
return REDIS_ESYS;
}
if (sdslen(h->wbuf)) {
nwritten = redis_fd_write(h->fd, h->wbuf, sdslen(h->wbuf));
if (nwritten < 0) {
/* Let all errors bubble, including EAGAIN */
return nwritten;
}
if (nwritten) {
h->wbuf = sdsrange(h->wbuf, nwritten, -1);
}
}
if (drained) {
*drained = (sdslen(h->wbuf) == 0);
}
return REDIS_OK;
}
int redis_handle_write_to_buffer(redis_handle *h, const char *buf, size_t len) {
if (h->fd < 0) {
errno = EINVAL;
return REDIS_ESYS;
}
h->wbuf = sdscatlen(h->wbuf, buf, len);
return REDIS_OK;
}
int redis_handle_read_to_buffer(redis_handle *h) {
char buf[2048];
int nread;
if (h->fd < 0) {
errno = EINVAL;
return REDIS_ESYS;
}
nread = redis_fd_read(h->fd, buf, sizeof(buf));
if (nread < 0) {
return nread;
}
h->rbuf = sdscatlen(h->rbuf, buf, nread);
return 0;
}
int redis_handle_read_from_buffer(redis_handle *h, redis_protocol **p) {
size_t navail, nparsed;
if (h->fd < 0) {
errno = EINVAL;
return REDIS_ESYS;
}
assert(p != NULL);
*p = NULL;
navail = sdslen(h->rbuf);
if (navail) {
nparsed = redis_parser_execute(&h->parser, p, h->rbuf, navail);
/* Trim read buffer */
h->rbuf = sdsrange(h->rbuf, nparsed, -1);
/* Test for parse error */
if (nparsed < navail && *p == NULL) {
errno = redis_parser_err(&h->parser);
return REDIS_EPARSER;
}
}
return REDIS_OK;
}

42
src/handle.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef HIREDIS_HANDLE_H
#define HIREDIS_HANDLE_H 1
#include <sys/time.h>
#include "address.h"
#include "common.h"
#include "parser.h"
typedef struct redis_handle_s redis_handle;
struct redis_handle_s {
int fd;
struct timeval timeout;
redis_parser parser;
char *wbuf;
char *rbuf;
};
int redis_handle_init(redis_handle *h);
int redis_handle_close(redis_handle *h);
int redis_handle_destroy(redis_handle *h);
int redis_handle_set_timeout(redis_handle *h, unsigned long us);
unsigned long redis_handle_get_timeout(redis_handle *h);
int redis_handle_connect_address(redis_handle *h, const redis_address addr);
int redis_handle_connect_in(redis_handle *h, struct sockaddr_in sa);
int redis_handle_connect_in6(redis_handle *h, struct sockaddr_in6 sa);
int redis_handle_connect_un(redis_handle *h, struct sockaddr_un sa);
int redis_handle_connect_gai(redis_handle *h, int family, const char *host, int port, redis_address *addr);
int redis_handle_wait_connected(redis_handle *h);
int redis_handle_wait_readable(redis_handle *h);
int redis_handle_wait_writable(redis_handle *h);
int redis_handle_write_from_buffer(redis_handle *h, int *drained);
int redis_handle_write_to_buffer(redis_handle *h, const char *buf, size_t len);
int redis_handle_read_to_buffer(redis_handle *h);
int redis_handle_read_from_buffer(redis_handle *h, redis_protocol **p);
#endif

106
src/ngx-queue.h Normal file
View File

@ -0,0 +1,106 @@
/*
* Copyright (C) Igor Sysoev
*/
#ifndef _NGX_QUEUE_H_INCLUDED_
#define _NGX_QUEUE_H_INCLUDED_
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q
#define ngx_queue_empty(h) \
(h == (h)->prev)
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
#define ngx_queue_insert_after ngx_queue_insert_head
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
#define ngx_queue_head(h) \
(h)->next
#define ngx_queue_last(h) \
(h)->prev
#define ngx_queue_sentinel(h) \
(h)
#define ngx_queue_next(q) \
(q)->next
#define ngx_queue_prev(q) \
(q)->prev
#if (NGX_DEBUG)
#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next; \
(x)->prev = NULL; \
(x)->next = NULL
#else
#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next
#endif
#define ngx_queue_split(h, q, n) \
(n)->prev = (h)->prev; \
(n)->prev->next = n; \
(n)->next = q; \
(h)->prev = (q)->prev; \
(h)->prev->next = h; \
(q)->prev = n;
#define ngx_queue_add(h, n) \
(h)->prev->next = (n)->next; \
(n)->next->prev = (h)->prev; \
(h)->prev = (n)->prev; \
(h)->prev->next = h;
#define ngx_queue_data(q, type, link) \
(type *) ((unsigned char *) q - offsetof(type, link))
#define ngx_queue_foreach(q, h) \
for ((q) = ngx_queue_head(h); (q) != (h); (q) = ngx_queue_next(q))
#endif /* _NGX_QUEUE_H_INCLUDED_ */

178
src/object.c Normal file
View File

@ -0,0 +1,178 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "object.h"
void redis_object_free(redis_object **ptr) {
redis_object *obj = *ptr;
switch(obj->type) {
case REDIS_STRING:
case REDIS_STATUS:
case REDIS_ERROR:
if (obj->str != NULL) {
free(obj->str);
obj->str = NULL;
obj->len = 0;
}
break;
case REDIS_ARRAY:
if (obj->element != NULL) {
unsigned int j;
for (j = 0; j < obj->elements; j++) {
if (obj->element[j] != NULL) {
redis_object_free(&obj->element[j]);
}
}
free(obj->element);
obj->element = NULL;
obj->elements = 0;
}
break;
}
free(*ptr);
*ptr = NULL;
}
static redis_object *_object_create_from_protocol(redis_protocol *p) {
redis_object *obj, *parent;
/* Create object when the callback was not fired before. */
if (p->data == NULL) {
obj = p->data = malloc(sizeof(redis_object));
if (obj == NULL) {
return NULL;
}
obj->type = p->type;
obj->str = NULL;
obj->len = 0;
obj->element = NULL;
obj->elements = 0;
if (p->parent) {
parent = (redis_object*)p->parent->data;
assert(parent->type == REDIS_ARRAY);
parent->element[p->parent->cursor] = obj;
}
}
obj = (redis_object*)p->data;
assert(obj && obj->type == p->type);
return obj;
}
static int _object_string_cb(redis_parser *parser, redis_protocol *p, const char *buf, size_t len) {
redis_object *self;
char *dst;
((void)parser);
self = _object_create_from_protocol(p);
if (self == NULL) {
return -1;
}
if (self->type == REDIS_STRING) {
assert(p->size >= 0);
dst = self->str;
/* The size is known upfront: allocate memory */
if (dst == NULL) {
dst = malloc(p->size+1);
if (dst == NULL) {
return -1;
}
}
} else {
assert(p->size < 0);
/* The size is not known upfront: dynamically allocate memory */
dst = realloc(self->str, self->len + len + 1);
if (dst == NULL) {
return -1;
}
}
/* Copy provided buffer */
memcpy(dst + self->len, buf, len);
self->str = dst;
self->len += len;
self->str[self->len] = '\0';
return 0;
}
static int _object_array_cb(redis_parser *parser, redis_protocol *p, size_t len) {
redis_object *self;
((void)parser);
self = _object_create_from_protocol(p);
if (self == NULL) {
return -1;
}
if (len > 0) {
self->element = calloc(len, sizeof(redis_object*));
if (self->element == NULL) {
return -1;
}
}
self->elements = len;
return 0;
}
static int _object_integer_cb(redis_parser *parser, redis_protocol *p, int64_t value) {
redis_object *self;
((void)parser);
self = _object_create_from_protocol(p);
if (self == NULL) {
return -1;
}
self->integer = value;
return 0;
}
static int _object_nil_cb(redis_parser *parser, redis_protocol *p) {
redis_object *self;
((void)parser);
self = _object_create_from_protocol(p);
if (self == NULL) {
return -1;
}
return 0;
}
static void _object_destroy_cb(redis_parser *parser, redis_protocol *p) {
redis_object *self;
((void)parser);
self = p->data;
redis_object_free(&self);
}
/* Set of callbacks that can be passed to the parser. */
redis_parser_callbacks redis_object_parser_callbacks = {
.on_string = _object_string_cb,
.on_array = _object_array_cb,
.on_integer = _object_integer_cb,
.on_nil = _object_nil_cb,
.destroy = _object_destroy_cb
};

20
src/object.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef HIREDIS_OBJECT_H
#define HIREDIS_OBJECT_H 1
#include "parser.h"
typedef struct redis_object_s redis_object;
struct redis_object_s {
int type; /* Object type */
int64_t integer; /* Value for REDIS_INTEGER */
char *str; /* REDIS_STRING, REDIS_STATUS, REDIS_ERROR */
unsigned int len; /* String length */
struct redis_object_s **element; /* Actual elements in REDIS_ARRAY */
unsigned int elements; /* Number of elements REDIS_ARRAY */
};
void redis_object_free(redis_object **obj);
extern redis_parser_callbacks redis_object_parser_callbacks;
#endif

524
src/parser.c Normal file
View File

@ -0,0 +1,524 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "parser.h"
#define ERRNO(code) RPE_##code
#define SET_ERRNO(code) do { \
parser->err = ERRNO(code); \
} while(0)
/* The redis_protocol argument to the callback function must be provided by
* the caller because "ISO C99 requires rest arguments to be used". */
#define CALLBACK(X, ...) do { \
if (callbacks && callbacks->on_##X) { \
if (callbacks->on_##X(parser, __VA_ARGS__) != 0) { \
SET_ERRNO(CALLBACK); \
goto error; \
} \
} \
} while(0)
#define RESET_PROTOCOL(ptr) do { \
redis_protocol *__tmp = (ptr); \
__tmp->poff = 0; \
__tmp->plen = 0; \
__tmp->coff = 0; \
__tmp->clen = 0; \
__tmp->type = 0; \
__tmp->size = -1; \
__tmp->cursor = -1; \
__tmp->data = NULL; \
} while(0)
#define REDIS_PARSER_STATES(_X) \
_X(unused) /* = 0 in enum */ \
_X(type_char) \
_X(integer_start) \
_X(integer_pos_19) \
_X(integer_pos_09) \
_X(integer_neg_19) \
_X(integer_neg_09) \
_X(integer_cr) \
_X(integer_lf) \
_X(bulk) \
_X(bulk_cr) \
_X(bulk_lf) \
_X(line) \
_X(line_lf) \
#define _GEN(name) s_##name,
enum state {
REDIS_PARSER_STATES(_GEN)
};
#undef _GEN
#define _GEN(code, description) description,
static const char *strerror_map[] = {
REDIS_PARSER_ERRNO_MAP(_GEN)
};
#undef _GEN
#define STATE(st) \
case s_##st: \
l_##st: \
state = s_##st; \
if (pos >= end) { /* No more data */ \
goto finalize; \
}
#define ADVANCE(bytes) do { \
pos += (bytes); nread += (bytes); \
} while(0)
#define MOVE(st) do { \
goto l_##st; \
} while(0)
#define ADVANCE_AND_MOVE(st) do { \
ADVANCE(1); \
MOVE(st); \
} while(0)
#ifdef DEBUG
#define _ENUM_GEN(s) #s,
static const char *state_str[] = {
REDIS_PARSER_STATES(_ENUM_GEN)
};
#undef _ENUM_GEN
#define LOG(fmt, ...) do { \
fprintf(stderr, fmt "\n", __VA_ARGS__); \
fflush(stderr); \
} while(0)
#include <ctype.h>
/* Can hold 10 char representations per LOG call */
static char _chrtos_buf[10][8];
static int _chrtos_idx = 0;
static const char *chrtos(char byte) {
char *buf = _chrtos_buf[_chrtos_idx++ %
(sizeof(_chrtos_buf) / sizeof(_chrtos_buf[0]))];
switch(byte) {
case '\\':
case '"':
sprintf(buf,"\\%c",byte);
break;
case '\n': sprintf(buf,"\\n"); break;
case '\r': sprintf(buf,"\\r"); break;
case '\t': sprintf(buf,"\\t"); break;
case '\a': sprintf(buf,"\\a"); break;
case '\b': sprintf(buf,"\\b"); break;
default:
if (isprint(byte))
sprintf(buf,"%c",byte);
else
sprintf(buf,"\\x%02x",(unsigned char)byte);
break;
}
return buf;
}
#else
#define LOG(fmt, ...) do { ; } while (0)
#endif
void redis_parser_init(redis_parser *parser, const redis_parser_callbacks *callbacks) {
parser->stackidx = -1;
parser->callbacks = callbacks;
parser->err = 0;
}
/* Execute the parser against len bytes in buf. When a full message was read,
* the "dst" pointer is populated with the address of the root payload object.
* This pointer is set to NULL when no full message could be parsed.
*
* This function returns the number of bytes that could be parsed. When no full
* message was parsed and the return value is smaller than the number of bytes
* that were available, an error occured and the parser should be
* re-initialized before parsing more data.
*/
size_t redis_parser_execute(redis_parser *parser, redis_protocol **dst, const char *buf, size_t len) {
redis_protocol *stack = parser->stack;
const redis_parser_callbacks *callbacks = parser->callbacks;
const char *pos;
const char *end;
size_t nread;
int stackidx;
unsigned char state;
struct redis_parser_int64_s i64;
redis_protocol *cur;
/* Abort immediately if the parser is in an error state. It should be
* re-initialized before attempting to execute it with new data. */
if (parser->err) {
return 0;
}
/* Reset destination */
if (dst) *dst = NULL;
/* Reset root protocol object for new messages */
if (parser->stackidx == -1) {
RESET_PROTOCOL(&stack[0]);
parser->nread = 0;
parser->stackidx = 0;
parser->state = s_type_char;
}
pos = buf;
end = buf+len;
nread = parser->nread;
stackidx = parser->stackidx;
state = parser->state;
i64 = parser->i64;
while (pos < end && stackidx >= 0) {
cur = &stack[stackidx];
cur->parent = stackidx > 0 ? &stack[stackidx-1] : NULL;
switch (state) {
STATE(type_char) {
cur->poff = nread;
switch (*pos) {
case '$':
cur->type = REDIS_STRING;
ADVANCE_AND_MOVE(integer_start);
case '*':
cur->type = REDIS_ARRAY;
ADVANCE_AND_MOVE(integer_start);
case ':':
cur->type = REDIS_INTEGER;
ADVANCE_AND_MOVE(integer_start);
case '+':
cur->type = REDIS_STATUS;
cur->cursor = 0;
ADVANCE_AND_MOVE(line);
case '-':
cur->type = REDIS_ERROR;
cur->cursor = 0;
ADVANCE_AND_MOVE(line);
}
SET_ERRNO(INVALID_TYPE);
goto error;
}
STATE(integer_start) {
char ch = *pos;
i64.i64 = 0;
i64.ui64 = 0;
/* Start unsigned, thus positive */
if (ch >= '1' && ch <= '9') {
i64.ui64 = ch - '0';
ADVANCE_AND_MOVE(integer_pos_09);
}
/* Start with negative sign */
if (ch == '-') {
ADVANCE_AND_MOVE(integer_neg_19);
}
/* Start with positive sign */
if (ch == '+') {
ADVANCE_AND_MOVE(integer_pos_19);
}
/* Single integer character is a zero */
if (ch == '0') {
ADVANCE_AND_MOVE(integer_cr);
}
SET_ERRNO(INVALID_INT);
goto error;
}
STATE(integer_pos_19) {
char ch = *pos;
if (ch >= '1' && ch <= '9') {
i64.ui64 = ch - '0';
ADVANCE_AND_MOVE(integer_pos_09);
}
SET_ERRNO(INVALID_INT);
goto error;
}
STATE(integer_pos_09) {
char ch = *pos;
if (ch >= '0' && ch <= '9') {
if (i64.ui64 > ((uint64_t)INT64_MAX / 10)) { /* Overflow */
SET_ERRNO(OVERFLOW);
goto error;
}
i64.ui64 *= 10;
if (i64.ui64 > ((uint64_t)INT64_MAX - (ch - '0'))) { /* Overflow */
SET_ERRNO(OVERFLOW);
goto error;
}
i64.ui64 += ch - '0';
ADVANCE_AND_MOVE(integer_pos_09);
} else if (ch == '\r') {
i64.i64 = i64.ui64;
ADVANCE_AND_MOVE(integer_lf);
}
SET_ERRNO(INVALID_INT);
goto error;
}
STATE(integer_neg_19) {
char ch = *pos;
if (ch >= '1' && ch <= '9') {
i64.ui64 = ch - '0';
ADVANCE_AND_MOVE(integer_neg_09);
}
SET_ERRNO(INVALID_INT);
goto error;
}
STATE(integer_neg_09) {
char ch = *pos;
if (ch >= '0' && ch <= '9') {
if (i64.ui64 > (((uint64_t)-(INT64_MIN+1)+1) / 10)) { /* Overflow */
SET_ERRNO(OVERFLOW);
goto error;
}
i64.ui64 *= 10;
if (i64.ui64 > (((uint64_t)-(INT64_MIN+1)+1) - (ch - '0'))) { /* Overflow */
SET_ERRNO(OVERFLOW);
goto error;
}
i64.ui64 += ch - '0';
ADVANCE_AND_MOVE(integer_neg_09);
} else if (ch == '\r') {
i64.i64 = -i64.ui64;
ADVANCE_AND_MOVE(integer_lf);
}
SET_ERRNO(INVALID_INT);
goto error;
}
STATE(integer_cr) {
if (*pos == '\r') {
ADVANCE_AND_MOVE(integer_lf);
}
SET_ERRNO(EXPECTED_CR);
goto error;
}
STATE(integer_lf) {
if (*pos != '\n') {
SET_ERRNO(EXPECTED_LF);
goto error;
}
/* Protocol length can be set regardless of type */
cur->plen = nread - cur->poff + 1; /* include \n */
if (cur->type == REDIS_STRING) {
if (i64.i64 < 0) { /* nil bulk */
cur->type = REDIS_NIL;
CALLBACK(nil, cur);
goto done;
}
/* Setup content offset and length */
cur->coff = nread + 1; /* include \n */
cur->clen = (unsigned)i64.i64;
cur->plen += cur->clen + 2; /* include \r\n */
/* Store size of complete bulk */
cur->size = (unsigned)i64.i64;
cur->cursor = 0;
ADVANCE_AND_MOVE(bulk);
}
if (cur->type == REDIS_ARRAY) {
if (i64.i64 < 0) { /* nil multi bulk */
cur->type = REDIS_NIL;
CALLBACK(nil, cur);
goto done;
}
/* Store size of complete multi bulk */
cur->size = (unsigned)i64.i64;
cur->cursor = 0;
CALLBACK(array, cur, cur->size);
goto done;
}
if (cur->type == REDIS_INTEGER) {
/* Setup content offset and length */
cur->coff = cur->poff + 1;
cur->clen = nread - cur->coff - 1; /* remove \r */
CALLBACK(integer, cur, i64.i64);
goto done;
}
assert(NULL && "unexpected object type in s_integer_lf");
}
STATE(bulk) {
size_t remaining = cur->size - cur->cursor;
size_t available = (end-pos);
/* Everything can be read */
if (remaining <= available) {
CALLBACK(string, cur, pos, remaining);
ADVANCE(remaining);
MOVE(bulk_cr);
}
/* Not everything can be read */
CALLBACK(string, cur, pos, available);
ADVANCE(available);
/* Add number of processed bytes to cursor */
cur->cursor += available;
goto finalize;
}
STATE(bulk_cr) {
if (*pos == '\r') {
ADVANCE_AND_MOVE(bulk_lf);
}
SET_ERRNO(EXPECTED_CR);
goto error;
}
STATE(bulk_lf) {
if (*pos == '\n') {
goto done;
}
SET_ERRNO(EXPECTED_LF);
goto error;
}
STATE(line) {
const char *mark = pos;
/* Remove tight loop and add function-wide "line mark" once
* limits on line length are added. */
while(pos < end) {
if (*pos == '\r') {
cur->coff = cur->poff + 1;
cur->clen = nread - cur->coff;
CALLBACK(string, cur, mark, pos-mark);
ADVANCE_AND_MOVE(line_lf);
}
ADVANCE(1);
}
/* No more data */
CALLBACK(string, cur, mark, pos-mark);
/* Add number of processed bytes to cursor */
cur->cursor += pos-mark;
goto finalize;
}
STATE(line_lf) {
if (*pos == '\n') {
cur->plen = nread - cur->poff + 1; /* include \n */
goto done;
}
SET_ERRNO(EXPECTED_LF);
goto error;
}
}
/* Transitions should be made from within the switch */
assert(NULL && "invalid code path");
done:
/* Message is done when root object is done */
do {
/* Move to nested object when we see an incomplete array */
if (cur->type == REDIS_ARRAY && (cur->cursor < cur->size)) {
RESET_PROTOCOL(&stack[++stackidx]);
break;
}
/* Aggregate plen for nested objects */
if (stackidx > 0) {
assert(stack[stackidx-1].type == REDIS_ARRAY);
stack[stackidx-1].plen += cur->plen;
stack[stackidx-1].cursor++;
}
cur = &stack[--stackidx];
} while (stackidx >= 0);
/* Always move back to start state */
state = s_type_char;
ADVANCE(1);
continue;
}
/* Set destination pointer when full message was read */
if (stackidx == -1) {
if (dst) *dst = &stack[0];
}
finalize:
parser->nread = nread;
parser->stackidx = stackidx;
parser->state = state;
parser->i64 = i64;
return pos-buf;
error:
if (parser->err == ERRNO(OK)) {
SET_ERRNO(UNKNOWN);
}
return pos-buf;
}
void redis_parser_destroy(redis_parser *parser) {
/* Only trigger destroy callback when the parser has unfinished
* redis_protocol instances. */
if (parser->stackidx >= 0 &&
parser->callbacks &&
parser->callbacks->destroy)
{
parser->callbacks->destroy(parser, redis_parser_root(parser));
}
}
redis_protocol *redis_parser_root(redis_parser *parser) {
return &parser->stack[0];
}
enum redis_parser_errno redis_parser_err(redis_parser *parser) {
return parser->err;
}
const char *redis_parser_strerror(enum redis_parser_errno err) {
if (err < (sizeof(strerror_map)/sizeof(strerror_map[0])))
return strerror_map[err];
return NULL;
}

96
src/parser.h Normal file
View File

@ -0,0 +1,96 @@
#ifndef HIREDIS_PARSER_H
#define HIREDIS_PARSER_H 1
#include <stdint.h>
/* Compat */
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_STRING REDIS_REPLY_STRING
#define REDIS_ARRAY REDIS_REPLY_ARRAY
#define REDIS_INTEGER REDIS_REPLY_INTEGER
#define REDIS_NIL REDIS_REPLY_NIL
#define REDIS_STATUS REDIS_REPLY_STATUS
#define REDIS_ERROR REDIS_REPLY_ERROR
typedef struct redis_parser_callbacks_s redis_parser_callbacks;
typedef struct redis_protocol_s redis_protocol;
typedef struct redis_parser_s redis_parser;
typedef int (*redis_parser_string_cb)(redis_parser *, redis_protocol *, const char *, size_t);
typedef int (*redis_parser_array_cb)(redis_parser *, redis_protocol *, size_t);
typedef int (*redis_parser_integer_cb)(redis_parser *, redis_protocol *, int64_t);
typedef int (*redis_parser_nil_cb)(redis_parser *, redis_protocol *);
typedef void (*redis_parser_destroy_cb)(redis_parser *, redis_protocol *);
struct redis_parser_callbacks_s {
redis_parser_string_cb on_string;
redis_parser_array_cb on_array;
redis_parser_integer_cb on_integer;
redis_parser_nil_cb on_nil;
redis_parser_destroy_cb destroy;
};
#define REDIS_PARSER_ERRNO_MAP(_X) \
_X(OK, NULL) /* = 0 in enum */ \
_X(UNKNOWN, "unknown") \
_X(CALLBACK, "callback failed") \
_X(INVALID_TYPE, "invalid type character") \
_X(INVALID_INT, "invalid integer character") \
_X(OVERFLOW, "overflow") \
_X(EXPECTED_CR, "expected \\r") \
_X(EXPECTED_LF, "expected \\n")
#define _REDIS_PARSER_ERRNO_GEN(code, description) RPE_##code,
enum redis_parser_errno {
REDIS_PARSER_ERRNO_MAP(_REDIS_PARSER_ERRNO_GEN)
};
#undef _REDIS_PARSER_ERRNO_GEN
struct redis_protocol_s {
unsigned char type; /* payload type */
void *data; /* payload data (to be populated by the callback functions) */
const redis_protocol* parent; /* when nested, parent object */
int size; /* size of complete bulk (bytes)/multi bulk (nested objects) */
int cursor; /* number of processed bytes/nested objects */
size_t poff; /* protocol offset */
size_t plen; /* protocol length */
size_t coff; /* content offset */
size_t clen; /* content length */
};
struct redis_parser_s {
/* private: callbacks */
const redis_parser_callbacks *callbacks;
/* private: number of consumed bytes for a single message */
size_t nread;
/* private: protocol_t stack (multi-bulk, nested multi-bulk) */
redis_protocol stack[3];
int stackidx;
/* private: parser state */
unsigned char state;
enum redis_parser_errno err;
/* private: temporary integer (integer reply, bulk length) */
struct redis_parser_int64_s {
uint64_t ui64; /* accumulator */
int64_t i64; /* result */
} i64;
};
void redis_parser_init(redis_parser *parser, const redis_parser_callbacks *callbacks);
size_t redis_parser_execute(redis_parser *parser, redis_protocol **dst, const char *buf, size_t len);
void redis_parser_destroy(redis_parser *parser);
redis_protocol *redis_parser_root(redis_parser *parser);
enum redis_parser_errno redis_parser_err(redis_parser *parser);
const char *redis_parser_strerror(enum redis_parser_errno err);
#endif // _REDIS_PARSER_H

230
src/request.c Normal file
View File

@ -0,0 +1,230 @@
#include <assert.h>
#include <errno.h>
#include <string.h>
#include "handle.h"
#include "object.h"
#include "request.h"
int redis_request_init(redis_request *self) {
memset(self, 0, sizeof(*self));
ngx_queue_init(&self->wq);
ngx_queue_init(&self->rq);
return REDIS_OK;
}
void redis_request_destroy(redis_request *self) {
ngx_queue_remove(&self->rq);
ngx_queue_remove(&self->wq);
self->free(self);
}
int redis_request_queue_init(redis_request_queue *self) {
memset(self, 0, sizeof(*self));
ngx_queue_init(&self->request_to_write);
ngx_queue_init(&self->request_wait_write);
ngx_queue_init(&self->request_wait_read);
redis_parser_init(&self->parser, &redis_object_parser_callbacks);
return REDIS_OK;
}
int redis_request_queue_destroy(redis_request_queue *self) {
ngx_queue_t *q;
redis_request *req;
while (!ngx_queue_empty(&self->request_wait_read)) {
q = ngx_queue_last(&self->request_wait_read);
req = ngx_queue_data(q, redis_request, rq);
redis_request_destroy(req);
}
while (!ngx_queue_empty(&self->request_wait_write)) {
q = ngx_queue_last(&self->request_wait_write);
req = ngx_queue_data(q, redis_request, wq);
redis_request_destroy(req);
}
while (!ngx_queue_empty(&self->request_to_write)) {
q = ngx_queue_last(&self->request_to_write);
req = ngx_queue_data(q, redis_request, wq);
redis_request_destroy(req);
}
redis_parser_destroy(&self->parser);
return REDIS_OK;
}
void redis_request_queue_write(redis_request_queue *self) {
if (self->write_fn) {
self->write_fn(self);
}
}
void redis_request_queue_start_read(redis_request_queue *self) {
if (self->start_read_fn) {
self->start_read_fn(self);
}
}
void redis_request_queue_stop_read(redis_request_queue *self) {
if (self->stop_read_fn) {
self->stop_read_fn(self);
}
}
void redis_request_queue_insert(redis_request_queue *self, redis_request *request) {
request->request_queue = self;
ngx_queue_insert_head(&self->request_to_write, &request->wq);
if (self->request_to_write_cb) {
self->request_to_write_cb(self, request);
}
/* Kickstart writes when this is the first pending write */
self->pending_writes++;
if (self->pending_writes == 1) {
redis_request_queue_write(self);
}
}
int redis_request_queue_write_ptr(redis_request_queue *self, const char **dstbuf, size_t *dstlen) {
ngx_queue_t *q = NULL;
redis_request *req = NULL;
const char *buf = NULL;
size_t len = 0;
int done = 0;
if (!ngx_queue_empty(&self->request_wait_write)) {
q = ngx_queue_head(&self->request_wait_write);
req = ngx_queue_data(q, redis_request, wq);
}
/* Continue until write_ptr returned a non-empty buffer */
while (len == 0) {
/* Make sure that `req` is assigned a non-done request */
if (req == NULL || req->write_ptr_done) {
if (ngx_queue_empty(&self->request_to_write)) {
return -1;
}
q = ngx_queue_last(&self->request_to_write);
req = ngx_queue_data(q, redis_request, wq);
/* Remove from tail of `to_write`, insert on head of `wait_write` */
ngx_queue_remove(q);
ngx_queue_insert_head(&self->request_wait_write, q);
if (self->request_wait_write_cb) {
self->request_wait_write_cb(self, req);
}
}
buf = NULL;
len = 0;
done = 0;
assert(req->write_ptr);
req->write_ptr(req, &buf, &len, &done);
req->write_ptr_len += len;
if (done) {
req->write_ptr_done = 1;
self->pending_writes--;
}
}
*dstbuf = buf;
*dstlen = len;
return 0;
}
int redis_request_queue_write_cb(redis_request_queue *self, size_t len) {
ngx_queue_t *q = NULL;
redis_request *req = NULL;
size_t nwritten;
while (len) {
if (ngx_queue_empty(&self->request_wait_write)) {
return -1;
}
q = ngx_queue_last(&self->request_wait_write);
req = ngx_queue_data(q, redis_request, wq);
/* Add this request to `wait_read` if necessary */
if (ngx_queue_empty(&req->rq)) {
ngx_queue_insert_head(&self->request_wait_read, &req->rq);
if (self->request_wait_read_cb) {
self->request_wait_read_cb(self, req);
}
}
nwritten = req->write_ptr_len - req->write_cb_len;
if (nwritten > len) {
nwritten = len;
}
assert(req->write_cb);
req->write_cb(req, nwritten);
req->write_cb_len += nwritten;
/* Remove this request from `wait_write` when done writing */
if (req->write_ptr_done && req->write_ptr_len == req->write_cb_len) {
ngx_queue_remove(q);
ngx_queue_init(q);
req->write_cb_done = 1;
}
len -= nwritten;
}
return 0;
}
int redis_request_queue_read_cb(redis_request_queue *self, const char *buf, size_t len) {
ngx_queue_t *q = NULL;
redis_request *req = NULL;
redis_protocol *p = NULL;
size_t nparsed = 0;
int done;
while (len) {
if (ngx_queue_empty(&self->request_wait_read)) {
return -1;
}
q = ngx_queue_last(&self->request_wait_read);
req = ngx_queue_data(q, redis_request, rq);
done = 0;
nparsed = redis_parser_execute(&self->parser, &p, buf, len);
if (nparsed < len && p == NULL) {
errno = redis_parser_err(&self->parser);
return REDIS_EPARSER;
}
assert(req->read_cb);
req->read_cb(req, p, buf, nparsed, &done);
req->read_cb_len += nparsed;
/* Request cannot be done on partial reply */
assert(p || !done);
/* Update buffer */
buf += nparsed;
len -= nparsed;
/* Remove this request from `wait_read` when done reading */
if (done) {
ngx_queue_remove(q);
ngx_queue_init(q);
req->read_cb_done = 1;
redis_request_destroy(req);
}
}
return 0;
}

154
src/request.h Normal file
View File

@ -0,0 +1,154 @@
#ifndef HIREDIS_REQUEST_H
#define HIREDIS_REQUEST_H 1
#include <stddef.h>
#include "ngx-queue.h"
#include "parser.h"
typedef struct redis_request_s redis_request;
typedef struct redis_request_queue_s redis_request_queue;
/*
* Obtain a char* to a buffer representing (some part of) the request.
*
* Arguments:
* self the request as previously inserted in the queue
* buf buffer with request data
* len length of the buffer with request data
* done set to non-zero by the request when it doesn't have more ptrs
*/
typedef void (redis_request_write_ptr)(redis_request *self,
const char **buf,
size_t *len,
int *done);
/*
* Let the request know that (some part of) it has been written.
*
* Arguments:
* self the request as previously inserted in the queue
* n the number of bytes written
*/
typedef void (redis_request_write_cb)(redis_request *self, int n);
/*
* Let the request know the wire-level data that was fed to the parser on its
* behalf. This is merely a convenience function that can be used to buffer
* the wire-level representation of the response, for example.
*
* Arguments:
* self the request as previously inserted in the queue
* buf buffer with reply data
* len length of buffer with reply data
* done set to non-zero by the request when it has been read in full
*/
typedef void (redis_request_read_cb)(redis_request *self,
redis_protocol *p,
const char *buf,
size_t len,
int *done);
/*
* Free the request. This function is called after the last reply has been
* read, and passed to the request via `read_cb`.
*
* Arguments:
* self the request as previously inserted in the queue
*/
typedef void (redis_request_free)(redis_request *self);
#define REDIS_REQUEST_COMMON \
redis_request_queue *request_queue; \
ngx_queue_t wq, rq; \
unsigned write_ptr_done:1; \
unsigned write_cb_done:1; \
unsigned read_cb_done:1; \
size_t write_ptr_len; \
size_t write_cb_len; \
size_t read_cb_len; \
redis_request_write_ptr *write_ptr; \
redis_request_write_cb *write_cb; \
redis_request_read_cb *read_cb; \
redis_request_free *free;
struct redis_request_s {
REDIS_REQUEST_COMMON
};
/*
* These functions are called whenever a request is inserted in a new queue.
* When a request is initially inserted via the `redis_request_queue_insert`
* function, it ends up in the `to_write` queue and the `to_write_cb` callback
* is called. Before the request emits one or more pointers to its buffers, it
* is placed in the `wait_write` queue and the `wait_write_cb` callback is
* called. Finally, when one or more bytes of the request have been put on the
* wire, the request is placed in the `wait_read` queue and the `wait_read_cb`
* callback is called.
*
* Arguments:
* self request queue
* request request that was moved to a new queue
*/
typedef void (redis_request_queue_to_write_cb)(redis_request_queue *self,
redis_request *request);
typedef void (redis_request_queue_wait_write_cb)(redis_request_queue *self,
redis_request *request);
typedef void (redis_request_queue_wait_read_cb)(redis_request_queue *self,
redis_request *request);
/*
* This type of function is called when I/O needs to happen. All requests that
* a request queue takes in eventually need to be written to a socket. When a
* request is inserted while the I/O library was not yet busy draining the
* queue of requests to write, one of the I/O functions will be called to
* _kickstart_ writes. After the kickstart the I/O library is supposed to
* continue writing until it no longer can retrieve `char *`'s to write. After
* that, it should be kickstarted by user code when user code stopped emitting
* `char*`'s, or is automatically kickstarted when the queue of requests to
* write was drained.
*
* Arguments:
* self request queue
*/
typedef void (redis_request_queue_io_fn)(redis_request_queue *self);
struct redis_request_queue_s {
size_t pending_writes;
ngx_queue_t request_to_write;
ngx_queue_t request_wait_write;
ngx_queue_t request_wait_read;
redis_request_queue_to_write_cb *request_to_write_cb;
redis_request_queue_wait_write_cb *request_wait_write_cb;
redis_request_queue_wait_read_cb *request_wait_read_cb;
redis_request_queue_io_fn *write_fn;
redis_request_queue_io_fn *start_read_fn;
redis_request_queue_io_fn *stop_read_fn;
redis_parser parser;
void *data;
};
int redis_request_init(redis_request *self);
void redis_request_destroy(redis_request *self);
int redis_request_queue_init(redis_request_queue *self);
int redis_request_queue_destroy(redis_request_queue *self);
void redis_request_queue_start_write(redis_request_queue *self);
void redis_request_queue_start_read(redis_request_queue *self);
void redis_request_queue_stop_read(redis_request_queue *self);
void redis_request_queue_insert(redis_request_queue *self, redis_request *request);
int redis_request_queue_write_ptr(redis_request_queue *self, const char **buf, size_t *len);
int redis_request_queue_write_cb(redis_request_queue *self, size_t len);
int redis_request_queue_read_cb(redis_request_queue *self, const char *buf, size_t len);
#endif

View File

View File

2
test/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
test-*
!test-*.[ch]

30
test/Makefile Normal file
View File

@ -0,0 +1,30 @@
include ../src/Makefile.common
STD= -std=c99 -pedantic -D_POSIX_C_SOURCE=200112L
OPT= -O0
WARN= -Wall -W
HIREDIS_CFLAGS+= -I../src
TESTS=test-format test-parser test-object test-handle test-context test-request
OBJ=spawn.o net-helper.o
all: $(TESTS)
../src/$(STLIBNAME):
cd ../src && $(MAKE) $(STLIBNAME)
test-%: test-%.o $(OBJ) ../src/$(STLIBNAME)
$(HIREDIS_LD) -o $@ $^ $(FINAL_LIBS)
.c.o:
$(HIREDIS_CC) -c $<
clean:
rm -f *.o $(TESTS)
-include ./Makefile.dep
dep:
$(HIREDIS_CC) -MM *.c > Makefile.dep
.PHONY: all clean dep

18
test/Makefile.dep Normal file
View File

@ -0,0 +1,18 @@
net-helper.o: net-helper.c net-helper.h ../src/handle.h ../src/address.h \
../src/common.h ../src/parser.h
spawn.o: spawn.c spawn.h
test-context.o: test-context.c ../src/context.h ../src/handle.h \
../src/address.h ../src/common.h ../src/parser.h net-helper.h \
../src/object.h test-helper.h spawn.h
test-format.o: test-format.c ../src/format.h test-helper.h \
../src/handle.h ../src/address.h ../src/common.h ../src/parser.h \
spawn.h
test-handle.o: test-handle.c ../src/handle.h ../src/address.h \
../src/common.h ../src/parser.h test-helper.h spawn.h
test-object.o: test-object.c ../src/object.h ../src/parser.h \
test-helper.h ../src/handle.h ../src/address.h ../src/common.h spawn.h
test-parser.o: test-parser.c ../src/parser.h ../src/sds.h test-helper.h \
../src/handle.h ../src/address.h ../src/common.h spawn.h
test-request.o: test-request.c ../src/request.h ../src/ngx-queue.h \
../src/parser.h test-helper.h ../src/handle.h ../src/address.h \
../src/common.h spawn.h

59
test/net-helper.c Normal file
View File

@ -0,0 +1,59 @@
#include <errno.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "net-helper.h"
void run_server(void *ptr) {
run_server_args *args = ptr;
int family = args->address.sa_family;
int fd;
int rv;
fd = socket(family, SOCK_STREAM, 0);
if (fd == -1) {
fprintf(stderr, "socket: %s\n", strerror(errno));
exit(1);
}
if (family == AF_INET || family == AF_INET6) {
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
fprintf(stderr, "setsockopt: %s\n", strerror(errno));
exit(1);
}
}
rv = bind(fd, &args->address.sa_addr.addr, args->address.sa_addrlen);
if (rv == -1) {
fprintf(stderr, "bind: %s\n", strerror(errno));
exit(1);
}
rv = listen(fd, 128);
if (rv == -1) {
fprintf(stderr, "listen: %s\n", strerror(errno));
exit(1);
}
while (1) {
redis_address remote;
remote.sa_addrlen = sizeof(remote.sa_addr);
rv = accept(fd, &remote.sa_addr.addr, &remote.sa_addrlen);
if (rv == -1) {
fprintf(stderr, "accept: %s\n", strerror(errno));
exit(1);
}
/* Execute specified function, if given */
if (args->fn.ptr != NULL) {
args->fn.ptr(rv, args->fn.data);
}
}
}

18
test/net-helper.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef TEST_NET_HELPER
#define TEST_NET_HELPER 1
#include "handle.h"
typedef struct run_server_args_s run_server_args;
struct run_server_args_s {
redis_address address;
struct {
void (*ptr)(int fd, void *data);
void *data;
} fn;
};
void run_server(void *ptr);
#endif

70
test/spawn.c Normal file
View File

@ -0,0 +1,70 @@
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "spawn.h"
int pidc = 0;
pid_t *pidv = NULL;
void spawn_init(void) {
assert(pidc == 0);
assert(pidv == NULL);
}
void spawn_destroy(void) {
pid_t pid;
int i;
int rv;
/* Kill children */
for (i = 0; i < pidc; i++) {
int status;
pid = waitpid(pidv[i], &status, WNOHANG);
if (pid) {
continue;
}
rv = kill(pidv[i], SIGKILL);
if (rv == -1) {
fprintf(stderr, "kill: %s\n", strerror(errno));
exit(1);
}
pid = waitpid(pidv[i], &status, 0);
assert(pid == pidv[i]);
}
free(pidv);
pidc = 0;
pidv = NULL;
}
void spawn(spawn_function *fn, void *ptr) {
pid_t pid;
pid = fork();
if (pid) {
pidv = realloc(pidv, sizeof(pid_t) * (pidc + 1));
pidv[pidc++] = pid;
} else {
fn(ptr);
exit(0);
}
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 1000 * 1000;
/* Give the fork some time to boot... */
nanosleep(&ts, NULL);
}

10
test/spawn.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef TEST_SPAWN_H
#define TEST_SPAWN_H 1
typedef void (spawn_function)(void *);
void spawn_init(void);
void spawn_destroy(void);
void spawn(spawn_function *fn, void *ptr);
#endif

332
test/test-context.c Normal file
View File

@ -0,0 +1,332 @@
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "context.h"
#include "net-helper.h"
#include "object.h"
#include "test-helper.h"
#define SETUP_CONNECT() \
redis_context c; \
int rv; \
rv = redis_context_init(&c); \
assert_equal_return(rv, REDIS_OK); \
rv = redis_context_set_timeout(&c, 10000); \
assert_equal_return(rv, REDIS_OK);
TEST(connect_in_refused) {
SETUP_CONNECT();
redis_address addr = redis_address_in("127.0.0.1", redis_port() + 1);
rv = redis_context_connect_in(&c, addr.sa_addr.in);
assert_equal_return(rv, REDIS_ESYS);
assert_equal_int(errno, ECONNREFUSED);
/* Address shouldn't be set when no connection could be made */
assert_equal_int(c.address.sa_family, 0);
redis_context_destroy(&c);
}
TEST(connect_in6_refused) {
SETUP_CONNECT();
redis_address addr = redis_address_in6("::1", redis_port() + 1);
rv = redis_context_connect_in6(&c, addr.sa_addr.in6);
assert_equal_return(rv, REDIS_ESYS);
assert_equal_int(errno, ECONNREFUSED);
/* Address shouldn't be set when no connection could be made */
assert_equal_int(c.address.sa_family, 0);
redis_context_destroy(&c);
}
TEST(connect_un_noent) {
SETUP_CONNECT();
redis_address addr = redis_address_un("/tmp/idontexist.sock");
rv = redis_context_connect_un(&c, addr.sa_addr.un);
assert_equal_return(rv, REDIS_ESYS);
assert_equal_int(errno, ENOENT);
/* Address shouldn't be set when no connection could be made */
assert_equal_int(c.address.sa_family, 0);
redis_context_destroy(&c);
}
TEST(connect_timeout) {
SETUP_CONNECT();
redis_address addr = redis_address_in("10.255.255.254", redis_port());
long long t1, t2;
t1 = usec();
rv = redis_context_connect_in(&c, addr.sa_addr.in);
t2 = usec();
assert_equal_return(rv, REDIS_ESYS);
assert_equal_int(errno, ETIMEDOUT);
assert((t2 - t1) < 15000); /* 5ms of slack should be enough */
/* Address shouldn't be set when no connection could be made */
assert_equal_int(c.address.sa_family, 0);
redis_context_destroy(&c);
}
TEST(connect_success) {
SETUP_CONNECT();
redis_address addr = redis_address_in("127.0.0.1", redis_port());
rv = redis_context_connect_in(&c, addr.sa_addr.in);
assert_equal_return(rv, REDIS_OK);
/* Address should be set when connection was made */
assert_equal_int(c.address.sa_family, AF_INET);
redis_context_destroy(&c);
}
TEST(connect_gai_unknown_host) {
SETUP_CONNECT();
rv = redis_context_connect_gai(&c, "idontexist.foo", redis_port());
assert_equal_return(rv, REDIS_EGAI);
/* Don't care about the specific error for now. */
assert(1);
/* Address shouldn't be set when no connection could be made */
assert_equal_int(c.address.sa_family, 0);
redis_context_destroy(&c);
}
TEST(connect_gai_timeout) {
SETUP_CONNECT();
long long t1, t2;
t1 = usec();
rv = redis_context_connect_gai(&c, "10.255.255.254", redis_port());
t2 = usec();
assert_equal_return(rv, REDIS_ESYS);
assert_equal_int(errno, ETIMEDOUT);
assert((t2 - t1) < 15000); /* 5ms of slack should be enough */
/* Address shouldn't be set when no connection could be made */
assert_equal_int(c.address.sa_family, 0);
redis_context_destroy(&c);
}
TEST(connect_gai_success) {
SETUP_CONNECT();
rv = redis_context_connect_gai(&c, "localhost", redis_port());
assert_equal_return(rv, REDIS_OK);
/* Address should be set when connection was made */
assert_equal_int(c.address.sa_family, AF_INET);
redis_context_destroy(&c);
}
#define SETUP_CONNECTED() \
SETUP_CONNECT(); \
rv = redis_context_connect_gai(&c, "localhost", redis_port()); \
assert_equal_return(rv, REDIS_OK);
static redis_object *read_from_context(redis_context *ctx) {
redis_protocol *reply;
int rv;
rv = redis_context_read(ctx, &reply);
assert_equal_return(rv, REDIS_OK);
return (redis_object*)reply->data;
}
static void compare_ok_status(redis_object *obj) {
assert_equal_int(obj->type, REDIS_STATUS);
assert_equal_string(obj->str, "OK");
assert_equal_int(obj->len, 2);
}
static void compare_string(redis_object *obj, const char *str, size_t len) {
assert_equal_int(obj->type, REDIS_STRING);
assert_equal_string(obj->str, str);
assert_equal_int(obj->len, len);
}
TEST(write_command) {
SETUP_CONNECTED();
rv = redis_context_write_command(&c, "set foo bar");
assert_equal_return(rv, REDIS_OK);
compare_ok_status(read_from_context(&c));
/* Check that the command was executed as intended */
rv = redis_context_write_command(&c, "get foo");
assert_equal_return(rv, REDIS_OK);
compare_string(read_from_context(&c), "bar", 3);
redis_context_destroy(&c);
}
TEST(write_command_argv) {
SETUP_CONNECTED();
int argc1 = 3;
const char *argv1[] = { "set", "foo", "bar" };
size_t argvlen1[] = { 3, 3, 3 };
rv = redis_context_write_command_argv(&c, argc1, argv1, argvlen1);
assert_equal_return(rv, REDIS_OK);
compare_ok_status(read_from_context(&c));
/* Check that the command was executed as intended */
int argc2 = 2;
const char *argv2[] = { "get", argv1[1] };
size_t argvlen2[] = { 3, argvlen1[1] };
rv = redis_context_write_command_argv(&c, argc2, argv2, argvlen2);
assert_equal_return(rv, REDIS_OK);
compare_string(read_from_context(&c), argv1[2], argvlen1[2]);
redis_context_destroy(&c);
}
TEST(call_command) {
SETUP_CONNECTED();
redis_protocol *reply;
rv = redis_context_call_command(&c, &reply, "set foo bar");
assert_equal_return(rv, REDIS_OK);
compare_ok_status(reply->data);
/* Check that the command was executed as intended */
rv = redis_context_call_command(&c, &reply, "get foo");
assert_equal_return(rv, REDIS_OK);
compare_string(reply->data, "bar", 3);
redis_context_destroy(&c);
}
TEST(call_command_argv) {
SETUP_CONNECTED();
redis_protocol *reply;
int argc1 = 3;
const char *argv1[] = { "set", "foo", "bar" };
size_t argvlen1[] = { 3, 3, 3 };
rv = redis_context_call_command_argv(&c, &reply, argc1, argv1, argvlen1);
assert_equal_return(rv, REDIS_OK);
compare_ok_status(reply->data);
/* Check that the command was executed as intended */
int argc2 = 2;
const char *argv2[] = { "get", argv1[1] };
size_t argvlen2[] = { 3, argvlen1[1] };
rv = redis_context_call_command_argv(&c, &reply, argc2, argv2, argvlen2);
assert_equal_return(rv, REDIS_OK);
compare_string(reply->data, argv1[2], argvlen1[2]);
redis_context_destroy(&c);
}
void run_server_ignore_connection(int fd, void *data) {
((void) fd);
((void) data);
}
TEST(flush_against_full_kernel_buffer) {
SETUP_CONNECT();
redis_address addr = redis_address_in("127.0.0.1", redis_port() + 1);
run_server_args args;
args.address = addr;
args.fn.ptr = run_server_ignore_connection;
spawn(run_server, &args);
rv = redis_context_connect_in(&c, addr.sa_addr.in);
assert_equal_return(rv, REDIS_OK);
/* Now write and flush until error */
while (1) {
rv = redis_context_write_command(&c, "ping");
assert_equal_return(rv, REDIS_OK);
rv = redis_context_flush(&c);
if (rv != REDIS_OK) {
break;
}
}
/* When the write buffer cannot be flushed, the operation should time out
* instead of directly returning EAGAIN to the caller */
assert_equal_return(rv, REDIS_ESYS);
assert_equal_int(errno, ETIMEDOUT);
}
void run_server_close_after_accept(int fd, void *data) {
((void) data);
close(fd);
}
TEST(read_against_closed_connection) {
SETUP_CONNECT();
redis_address addr = redis_address_in("127.0.0.1", redis_port() + 1);
run_server_args args;
args.address = addr;
args.fn.ptr = run_server_close_after_accept;
spawn(run_server, &args);
rv = redis_context_connect_in(&c, addr.sa_addr.in);
assert_equal_return(rv, REDIS_OK);
redis_protocol *reply = NULL;
/* Read should immediately EOF */
rv = redis_context_read(&c, &reply);
assert_equal_return(rv, REDIS_EEOF);
/* ... and should be idempotent... */
rv = redis_context_read(&c, &reply);
assert_equal_return(rv, REDIS_EEOF);
redis_context_destroy(&c);
}
int main(void) {
test_connect_in_refused();
test_connect_in6_refused();
test_connect_un_noent();
test_connect_timeout();
test_connect_success();
test_connect_gai_unknown_host();
test_connect_gai_timeout();
test_connect_gai_success();
test_write_command();
test_write_command_argv();
test_call_command();
test_call_command_argv();
test_flush_against_full_kernel_buffer();
test_read_against_closed_connection();
}

146
test/test-format.c Normal file
View File

@ -0,0 +1,146 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "format.h"
#include "test-helper.h"
#define SETUP() \
char *cmd; \
int len;
TEST(format_without_interpolation) {
SETUP();
len = redis_format_command(&cmd, "SET foo bar");
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
}
TEST(format_with_string_interpolation) {
SETUP();
/* Regular */
len = redis_format_command(&cmd, "SET %s %s", "foo", "bar");
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
/* With empty string */
len = redis_format_command(&cmd, "SET %s %s", "foo", "");
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
free(cmd);
/* Empty string between non-empty args */
len = redis_format_command(&cmd, "SET %s %s", "", "bar");
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nbar\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(0+2)+4+(3+2));
free(cmd);
}
TEST(format_with_binary_interpolation) {
SETUP();
/* Regular */
len = redis_format_command(&cmd, "SET %b %b", "f\0o", 3, "b\0r", 3);
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nf\0o\r\n$3\r\nb\0r\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
/* With empty string */
len = redis_format_command(&cmd, "SET %b %b", "f\0o", 3, "", 0);
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nf\0o\r\n$0\r\n\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
free(cmd);
/* Empty string between non-empty args */
len = redis_format_command(&cmd, "SET %b %b", "", 0, "b\0r", 3);
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nb\0r\r\n", len) == 0 &&
len == 4+4+(3+2)+4+(0+2)+4+(3+2));
free(cmd);
}
TEST(format_with_literal_percent) {
SETUP();
len = redis_format_command(&cmd,"SET %% %%");
assert(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
free(cmd);
}
TEST(format_with_printf_format) {
SETUP();
/* Vararg width depends on the type. These tests make sure that the
* width is correctly determined using the format and subsequent varargs
* can correctly be interpolated. */
#define INTEGER_WIDTH_TEST(fmt, type) do { \
type value = 123; \
len = redis_format_command(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
assert(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
len == 4+5+(12+2)+4+(9+2)); \
free(cmd); \
} while(0)
#define FLOAT_WIDTH_TEST(type) do { \
type value = 123.0; \
len = redis_format_command(&cmd,"key:%08.3f str:%s", value, "hello"); \
assert(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
len == 4+5+(12+2)+4+(9+2)); \
free(cmd); \
} while(0)
INTEGER_WIDTH_TEST("d", int);
INTEGER_WIDTH_TEST("hhd", char);
INTEGER_WIDTH_TEST("hd", short);
INTEGER_WIDTH_TEST("ld", long);
INTEGER_WIDTH_TEST("lld", long long);
INTEGER_WIDTH_TEST("u", unsigned int);
INTEGER_WIDTH_TEST("hhu", unsigned char);
INTEGER_WIDTH_TEST("hu", unsigned short);
INTEGER_WIDTH_TEST("lu", unsigned long);
INTEGER_WIDTH_TEST("llu", unsigned long long);
FLOAT_WIDTH_TEST(float);
FLOAT_WIDTH_TEST(double);
}
TEST(format_with_invalid_printf_format) {
SETUP();
len = redis_format_command(&cmd,"key:%08p %b",1234,"foo",3);
assert(len == -1);
}
TEST(format_argv) {
SETUP();
int argc = 3;
const char *argv[3] = { "SET", "foo\0xxx", "bar" };
size_t lens[3] = { 3, 7, 3 };
/* Without argument length */
len = redis_format_command_argv(&cmd,argc,argv,NULL);
assert(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
/* With argument length */
len = redis_format_command_argv(&cmd,argc,argv,lens);
assert(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
free(cmd);
}
int main(void) {
test_format_without_interpolation();
test_format_with_string_interpolation();
test_format_with_binary_interpolation();
test_format_with_literal_percent();
test_format_with_printf_format();
test_format_with_invalid_printf_format();
test_format_argv();
}

265
test/test-handle.c Normal file
View File

@ -0,0 +1,265 @@
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "handle.h"
#include "test-helper.h"
TEST(connect_in_refused) {
redis_handle h;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(redis_port() + 1);
assert(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr) == 1);
rv = redis_handle_connect_in(&h, sa);
assert_equal_int(rv, REDIS_OK);
rv = redis_handle_wait_connected(&h);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, ECONNREFUSED);
redis_handle_destroy(&h);
}
TEST(connect_in6_refused) {
redis_handle h;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
struct sockaddr_in6 sa;
sa.sin6_family = AF_INET6;
sa.sin6_port = htons(redis_port() + 1);
assert(inet_pton(AF_INET6, "::1", &sa.sin6_addr) == 1);
rv = redis_handle_connect_in6(&h, sa);
assert_equal_int(rv, REDIS_OK);
rv = redis_handle_wait_connected(&h);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, ECONNREFUSED);
redis_handle_destroy(&h);
}
TEST(connect_un_noent) {
redis_handle h;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
struct sockaddr_un sa;
sa.sun_family = AF_UNIX;
strcpy((char*)&sa.sun_path, "/tmp/idontexist.sock");
rv = redis_handle_connect_un(&h, sa);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, ENOENT);
redis_handle_destroy(&h);
}
TEST(connect_timeout) {
redis_handle h;
long long t1, t2;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(redis_port());
assert(inet_pton(AF_INET, "10.255.255.254", &sa.sin_addr) == 1);
rv = redis_handle_set_timeout(&h, 10000);
assert(rv == REDIS_OK);
rv = redis_handle_connect_in(&h, sa);
assert_equal_int(rv, REDIS_OK);
t1 = usec();
rv = redis_handle_wait_connected(&h);
t2 = usec();
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, ETIMEDOUT);
assert((t2 - t1) < 15000); /* 5ms of slack should be enough */
redis_handle_destroy(&h);
}
TEST(connect_gai_unknown_host) {
redis_handle h;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
rv = redis_handle_connect_gai(&h, AF_INET, "idontexist.foo", redis_port(), NULL);
assert_equal_int(rv, REDIS_EGAI);
/* Don't care about the specific error for now. */
redis_handle_destroy(&h);
}
TEST(connect_gai_success) {
redis_handle h;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
rv = redis_handle_connect_gai(&h, AF_INET, "localhost", redis_port(), NULL);
assert_equal_int(rv, REDIS_OK);
rv = redis_handle_wait_connected(&h);
assert_equal_int(rv, REDIS_OK);
redis_handle_destroy(&h);
}
TEST(connect_gai_redis_address) {
redis_handle h;
redis_address address;
int rv;
rv = redis_handle_init(&h);
assert(rv == REDIS_OK);
rv = redis_handle_connect_gai(&h, AF_INET, "localhost", redis_port(), &address);
assert_equal_int(rv, REDIS_OK);
/* Match common fields */
assert(address.sa_family == AF_INET);
assert(address.sa_addrlen == sizeof(struct sockaddr_in));
/* Match sockaddr specific fields */
assert(address.sa_addr.in.sin_family == AF_INET);
assert(ntohs(address.sa_addr.in.sin_port) == redis_port());
assert(strcmp(inet_ntoa(address.sa_addr.in.sin_addr), "127.0.0.1") == 0);
redis_handle_destroy(&h);
}
redis_handle *setup(void) {
redis_handle *h = malloc(sizeof(*h));
int rv;
rv = redis_handle_init(h);
assert(rv == REDIS_OK);
rv = redis_handle_set_timeout(h, 10000);
assert_equal_int(rv, REDIS_OK);
rv = redis_handle_connect_gai(h, AF_INET, "localhost", redis_port(), NULL);
assert_equal_int(rv, REDIS_OK);
rv = redis_handle_wait_connected(h);
assert_equal_int(rv, REDIS_OK);
return h;
}
void teardown(redis_handle *h) {
redis_handle_destroy(h);
free(h);
}
TEST(eof_after_quit) {
redis_handle *h = setup();
int rv, drained;
rv = redis_handle_write_to_buffer(h, "quit\r\n", 6);
assert(rv == REDIS_OK);
rv = redis_handle_write_from_buffer(h, &drained);
assert(rv == REDIS_OK && drained);
rv = redis_handle_wait_readable(h);
assert(rv == REDIS_OK);
rv = redis_handle_read_to_buffer(h);
assert(rv == REDIS_OK);
redis_protocol *p;
rv = redis_handle_read_from_buffer(h, &p);
assert(rv == REDIS_OK);
assert_equal_int(p->type, REDIS_STATUS);
assert_equal_int(p->plen, 5); /* +ok\r\n */
/* wait_readable should return REDIS_OK because EOF is readable */
rv = redis_handle_wait_readable(h);
assert_equal_int(rv, REDIS_OK);
/* 1: read EOF */
rv = redis_handle_read_to_buffer(h);
assert_equal_int(rv, REDIS_EEOF);
/* 2: reading EOF should be idempotent (user is responsible for closing the handle) */
rv = redis_handle_read_to_buffer(h);
assert_equal_int(rv, REDIS_EEOF);
teardown(h);
}
TEST(read_timeout) {
redis_handle *h = setup();
int rv;
rv = redis_handle_wait_readable(h);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, ETIMEDOUT);
teardown(h);
}
TEST(einval_against_closed_handle) {
redis_handle *h = setup();
int rv;
rv = redis_handle_close(h);
assert_equal_int(rv, REDIS_OK);
rv = redis_handle_write_to_buffer(h, "ping\r\n", 6);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, EINVAL);
int drained;
rv = redis_handle_write_from_buffer(h, &drained);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, EINVAL);
rv = redis_handle_read_to_buffer(h);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, EINVAL);
redis_protocol *p;
rv = redis_handle_read_from_buffer(h, &p);
assert_equal_int(rv, REDIS_ESYS);
assert_equal_int(errno, EINVAL);
teardown(h);
}
int main(void) {
test_connect_in_refused();
test_connect_in6_refused();
test_connect_un_noent();
test_connect_timeout();
test_connect_gai_unknown_host();
test_connect_gai_success();
test_connect_gai_redis_address();
test_eof_after_quit();
test_read_timeout();
test_einval_against_closed_handle();
}

149
test/test-helper.h Normal file
View File

@ -0,0 +1,149 @@
#ifndef _TEST_HELPER_H
#define _TEST_HELPER_H 1
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include "handle.h"
#include "spawn.h"
#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif
/******************************************************************************/
/* ASSERTS ********************************************************************/
/******************************************************************************/
#define assert_equal(a, b, type, fmt) do { \
type a_ = (a); \
type b_ = (b); \
if (a_ != b_) { \
fprintf(stderr, \
"%s:%d: " fmt " != " fmt "\n", \
__FILE__, __LINE__, a_, b_); \
assert(0); \
} \
} while(0)
#define assert_equal_int(a,b) do { \
assert_equal(a, b, int, "%d"); \
} while(0)
#define assert_equal_size_t(a,b) do { \
assert_equal(a, b, size_t, "%lu"); \
} while(0)
/* Use long long to avoid compiler warnings about the printf format. */
#define assert_equal_int64_t(a,b) do { \
assert_equal(a, b, long long, "%lld"); \
} while(0)
#define assert_equal_double(a,b) do { \
assert_equal(a, b, double, "%f"); \
} while(0)
#define assert_equal_string(a,b) do { \
if (strcmp(a, b)) { \
fprintf(stderr, \
"%s:%d: %s != %s\n", \
__FILE__, __LINE__, a, b); \
assert(0); \
} \
} while(0)
#define ERR_TO_STR_OFFSET 4
UNUSED static const char *err_to_str[] = {
[REDIS_OK + ERR_TO_STR_OFFSET] = "REDIS_OK",
[REDIS_ESYS + ERR_TO_STR_OFFSET] = "REDIS_ESYS",
[REDIS_EGAI + ERR_TO_STR_OFFSET] = "REDIS_EGAI",
[REDIS_EPARSER + ERR_TO_STR_OFFSET] = "REDIS_EPARSER",
[REDIS_EEOF + ERR_TO_STR_OFFSET] = "REDIS_EEOF"
};
UNUSED static void assert_equal_return_failure(int actual, int expected, const char *file, int line) {
const char *actualstr = err_to_str[actual + ERR_TO_STR_OFFSET];
const char *expectedstr = err_to_str[expected + ERR_TO_STR_OFFSET];
char reason[128];
switch (actual) {
case REDIS_ESYS:
sprintf(reason, "%d:%s", errno, strerror(errno));
break;
case REDIS_EGAI:
sprintf(reason, "%d:%s", errno, gai_strerror(errno));
break;
}
fprintf(stderr, "%s:%d: ", file, line);
fprintf(stderr, "%s != %s (%s)\n", actualstr, expectedstr, reason);
}
#define assert_equal_return(a, b) do { \
if ((a) != (b)) { \
assert_equal_return_failure( \
(a), (b), \
__FILE__, __LINE__); \
assert(0); \
} \
} while(0)
/******************************************************************************/
/* TEST DEFINITION ************************************************************/
/******************************************************************************/
UNUSED static const char *current_test = NULL;
#define TEST(name) \
static void test__##name(void); \
static void test_##name(void) { \
current_test = #name; \
spawn_init(); \
test__##name(); \
spawn_destroy(); \
printf("%s: PASSED\n", current_test); \
} \
static void test__##name(void)
/******************************************************************************/
/* TIMING *********************************************************************/
/******************************************************************************/
UNUSED static long long usec(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (long long)tv.tv_sec * 1000000 + tv.tv_usec;
}
/******************************************************************************/
/* MISC ***********************************************************************/
/******************************************************************************/
#define REDIS_DEFAULT_PORT 6379
UNUSED static int redis_port(void) {
char *env, *eptr;
long port = REDIS_DEFAULT_PORT;
env = getenv("REDIS_PORT");
if (env != NULL) {
port = strtol(env, &eptr, 10);
/* Reset to default when the var contains garbage. */
if (*eptr != '\0') {
port = REDIS_DEFAULT_PORT;
}
}
return port;
}
#endif

259
test/test-object.c Normal file
View File

@ -0,0 +1,259 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "object.h"
#include "parser.h"
#include "test-helper.h"
void test_string(redis_parser *parser) {
const char *buf = "$5\r\nhello\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, 11) == 11);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_STRING);
assert_equal_string(obj->str, "hello");
assert_equal_int(obj->len, 5);
redis_object_free(&obj);
}
void test_chunked_string(redis_parser *parser) {
const char *buf = "$5\r\nhello\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf+0, 6) == 6);
assert(redis_parser_execute(parser, &res, buf+6, 5) == 5);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_STRING);
assert_equal_string(obj->str, "hello");
assert_equal_int(obj->len, 5);
redis_object_free(&obj);
}
void test_empty_string(redis_parser *parser) {
const char *buf = "$0\r\n\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, 6) == 6);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_STRING);
assert_equal_string(obj->str, "");
assert_equal_int(obj->len, 0);
redis_object_free(&obj);
}
void test_nil(redis_parser *parser) {
const char *buf = "$-1\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, 5) == 5);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_NIL);
redis_object_free(&obj);
}
void test_array(redis_parser *parser) {
const char *buf =
"*2\r\n"
"$5\r\nhello\r\n"
"$5\r\nworld\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, 26) == 26);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_ARRAY);
assert_equal_int(obj->elements, 2);
assert_equal_int(obj->element[0]->type, REDIS_STRING);
assert_equal_string(obj->element[0]->str, "hello");
assert_equal_int(obj->element[0]->len, 5);
assert_equal_int(obj->element[1]->type, REDIS_STRING);
assert_equal_string(obj->element[1]->str, "world");
assert_equal_int(obj->element[1]->len, 5);
redis_object_free(&obj);
}
void test_empty_array(redis_parser *parser) {
const char *buf = "*0\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, 4) == 4);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_ARRAY);
assert_equal_int(obj->elements, 0);
redis_object_free(&obj);
}
void test_integer(redis_parser *parser) {
const char *buf = ":37\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, 5) == 5);
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_INTEGER);
assert_equal_int(obj->integer, 37);
redis_object_free(&obj);
}
void test_status(redis_parser *parser) {
const char *buf = "+foo\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_STATUS);
assert_equal_string(obj->str, "foo");
assert_equal_int(obj->len, 3);
redis_object_free(&obj);
}
void test_empty_status(redis_parser *parser) {
const char *buf = "+\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_STATUS);
assert_equal_string(obj->str, "");
assert_equal_int(obj->len, 0);
redis_object_free(&obj);
}
void test_error(redis_parser *parser) {
const char *buf = "-err\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_ERROR);
assert_equal_string(obj->str, "err");
assert_equal_int(obj->len, 3);
redis_object_free(&obj);
}
void test_empty_error(redis_parser *parser) {
const char *buf = "-\r\n";
redis_protocol *res;
redis_object *obj;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
obj = (redis_object*)res->data;
assert(obj != NULL);
assert_equal_int(obj->type, REDIS_ERROR);
assert_equal_string(obj->str, "");
assert_equal_int(obj->len, 0);
redis_object_free(&obj);
}
void test_destroy_callback(redis_parser *parser) {
const char *buf = "+ok\r";
redis_protocol *res;
redis_parser_init(parser, &redis_object_parser_callbacks);
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
assert(res == NULL);
assert(redis_parser_err(parser) == RPE_OK);
/* Go ahead and let the parser clean up */
redis_parser_destroy(parser);
/* No way to check if the temporary object gets properly free'd.
* Let's hope that Valgrind starts complaining when this is buggy. */
}
int main(int argc, char **argv) {
((void)argc);
((void)argv);
redis_parser *parser = malloc(sizeof(redis_parser));
printf("redis_object: %lu bytes\n", sizeof(redis_object));
test_string(parser);
test_chunked_string(parser);
test_empty_string(parser);
test_nil(parser);
test_array(parser);
test_empty_array(parser);
test_integer(parser);
test_status(parser);
test_empty_status(parser);
test_error(parser);
test_empty_error(parser);
test_destroy_callback(parser);
free(parser);
return 0;
}

650
test/test-parser.c Normal file
View File

@ -0,0 +1,650 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "parser.h"
#include "sds.h"
#include "test-helper.h"
typedef struct log_entry_s log_entry_t;
struct log_entry_s {
redis_protocol obj;
int type;
/* string_t specifics */
char string_buf[1024];
int string_size;
/* array_t specifics */
size_t array_len;
/* integer_t specifics */
int64_t integer_value;
};
#define CB_LOG_SIZE 10
static log_entry_t cb_log[CB_LOG_SIZE];
static int cb_log_idx = 0;
static void reset_cb_log(void) {
memset(cb_log, 0xff, sizeof(cb_log));
cb_log_idx = 0;
}
static log_entry_t *dup_cb_log(void) {
log_entry_t *log = malloc(sizeof(cb_log));
memcpy(log, cb_log, sizeof(cb_log));
return log;
}
int on_string(redis_parser *parser, redis_protocol *p, const char *buf, size_t len) {
((void)parser);
log_entry_t *log = (log_entry_t*)p->data;
/* Use new entry when this is the first call */
if (log == NULL) {
log = p->data = &cb_log[cb_log_idx++];
log->obj = *p;
log->type = p->type;
log->string_buf[0] = '\0';
log->string_size = p->size;
}
/* Type should never change when called multiple times */
assert(log->type == p->type);
/* Cursor should equal current string length */
assert(p->cursor == (signed)strlen(log->string_buf));
/* Append string data */
strncat(log->string_buf, buf, len);
return 0;
}
int on_array(redis_parser *parser, redis_protocol *p, size_t len) {
((void)parser);
log_entry_t *log = (log_entry_t*)p->data;
/* Should only be called once */
assert(log == NULL);
/* Use new entry */
log = p->data = &cb_log[cb_log_idx++];
log->obj = *p;
log->type = p->type;
log->array_len = len;
return 0;
}
int on_integer(redis_parser *parser, redis_protocol *p, int64_t value) {
((void)parser);
log_entry_t *log = (log_entry_t*)p->data;
/* Should only be called once */
assert(log == NULL);
/* Use new entry */
log = p->data = &cb_log[cb_log_idx++];
log->obj = *p;
log->type = p->type;
log->integer_value = value;
return 0;
}
int on_nil(redis_parser *parser, redis_protocol *p) {
((void)parser);
log_entry_t *log = (log_entry_t*)p->data;
/* Should only be called once */
assert(log == NULL);
/* Use new entry */
log = p->data = &cb_log[cb_log_idx++];
log->obj = *p;
log->type = p->type;
return 0;
}
static int destroy_called = 0;
void destroy(redis_parser *parser, redis_protocol *p) {
((void)parser);
((void)p);
destroy_called++;
}
static redis_parser_callbacks callbacks = {
&on_string,
&on_array,
&on_integer,
&on_nil,
&destroy
};
#define RESET_PARSER(__parser) do { \
reset_cb_log(); \
redis_parser_init((__parser), &callbacks); \
destroy_called = 0; \
} while(0)
void test_char_by_char(const char *buf, size_t len) {
log_entry_t *ref;
redis_parser *p;
redis_protocol *res;
size_t i, j, k;
ref = dup_cb_log();
p = malloc(sizeof(redis_parser));
for (i = 0; i < (len-1); i++) {
for (j = i+1; j < len; j++) {
RESET_PARSER(p);
#ifdef DEBUG
sds debug = sdsempty();
debug = sdscatrepr(debug, buf, i);
debug = sdscatprintf(debug, " + ");
debug = sdscatrepr(debug, buf+i, j-i);
debug = sdscatprintf(debug, " + ");
debug = sdscatrepr(debug, buf+j, len-j);
fprintf(stderr, "%s\n", debug);
sdsfree(debug);
#endif
/* Slice 1 */
assert_equal_size_t(redis_parser_execute(p, &res, buf, i), i);
assert(NULL == res); /* no result */
/* Slice 2 */
assert_equal_size_t(redis_parser_execute(p, &res, buf+i, j-i), j-i);
assert(NULL == res); /* no result */
/* Slice 3 */
assert_equal_size_t(redis_parser_execute(p, &res, buf+j, len-j), len-j);
assert(NULL != res);
/* Compare callback log with reference */
for (k = 0; k < CB_LOG_SIZE; k++) {
log_entry_t expect = ref[k];
log_entry_t actual = cb_log[k];
/* Not interested in the redis_protocol data */
memset(&expect.obj, 0, sizeof(expect.obj));
memset(&actual.obj, 0, sizeof(actual.obj));
assert(memcmp(&expect, &actual, sizeof(expect)) == 0);
}
}
}
free(p);
free(ref);
}
#define SETUP(var) \
redis_parser _parser, *(var) = &_parser; \
redis_parser_init((var), &callbacks);
/* Not a real test, just print sizeof's */
TEST(struct_size) {
printf("redis_protocol: %lu bytes\n", sizeof(redis_protocol));
printf("redis_parser: %lu bytes\n", sizeof(redis_parser));
}
TEST(string) {
SETUP(p);
const char *buf = "$5\r\nhello\r\n";
size_t len = 11;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_STRING);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 11);
assert_equal_size_t(res->coff, 4);
assert_equal_size_t(res->clen, 5);
/* Check callbacks */
assert(cb_log_idx == 1);
assert_equal_int(cb_log[0].string_size, 5);
assert(!strncmp(cb_log[0].string_buf, buf+4, 5));
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(empty_string) {
SETUP(p);
const char *buf = "$0\r\n\r\n";
size_t len = 6;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_STRING);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 6);
assert_equal_size_t(res->coff, 4);
assert_equal_size_t(res->clen, 0);
/* Check callbacks */
assert(cb_log_idx == 1);
assert_equal_int(cb_log[0].string_size, 0);
assert(!strncmp(cb_log[0].string_buf, buf+4, 0));
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(nil_string) {
SETUP(p);
const char *buf = "$-1\r\n";
size_t len = 5;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_NIL);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 5);
assert_equal_size_t(res->coff, 0);
assert_equal_size_t(res->clen, 0);
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(array) {
SETUP(p);
const char *buf =
"*2\r\n"
"$5\r\nhello\r\n"
"$5\r\nworld\r\n";
size_t len = 26;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_ARRAY);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 26);
assert_equal_size_t(res->coff, 0);
assert_equal_size_t(res->clen, 0);
/* Check callbacks */
assert_equal_size_t(cb_log_idx, 3);
assert_equal_size_t(cb_log[0].obj.poff, 0);
assert_equal_size_t(cb_log[0].obj.plen, 4);
assert_equal_size_t(cb_log[0].array_len, 2);
assert_equal_size_t(cb_log[1].obj.poff, 4);
assert_equal_size_t(cb_log[1].obj.plen, 4+5+2);
assert_equal_size_t(cb_log[1].obj.coff, 4+4);
assert_equal_size_t(cb_log[1].obj.clen, 5);
assert_equal_int(cb_log[1].string_size, 5);
assert(!strncmp(cb_log[1].string_buf, buf+4+4, 5));
assert_equal_size_t(cb_log[2].obj.poff, 4+11);
assert_equal_size_t(cb_log[2].obj.plen, 4+5+2);
assert_equal_size_t(cb_log[2].obj.coff, 4+11+4);
assert_equal_size_t(cb_log[2].obj.clen, 5);
assert_equal_int(cb_log[2].string_size, 5);
assert(!strncmp(cb_log[2].string_buf, buf+4+11+4, 5));
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(empty_array) {
SETUP(p);
const char *buf = "*0\r\n";
size_t len = 4;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_ARRAY);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 4);
/* Check callbacks */
assert_equal_size_t(cb_log_idx, 1);
assert_equal_size_t(cb_log[0].array_len, 0);
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(nil_array) {
SETUP(p);
const char *buf = "*-1\r\n";
size_t len = 5;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_NIL);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 5);
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(integer) {
SETUP(p);
const char *buf = ":1234\r\n";
size_t len = 7;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_INTEGER);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 7);
assert_equal_size_t(res->coff, 1);
assert_equal_size_t(res->clen, 4);
/* Check callbacks */
assert_equal_size_t(cb_log_idx, 1);
assert_equal_size_t(cb_log[0].integer_value, 1234);
/* Chunked check */
test_char_by_char(buf, len);
/* Negative sign */
buf = ":-123\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
assert(res != NULL);
assert_equal_size_t(cb_log_idx, 1);
assert_equal_int64_t(cb_log[0].integer_value, -123);
test_char_by_char(buf, strlen(buf));
/* Positive sign */
buf = ":+123\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
assert(res != NULL);
assert_equal_size_t(cb_log_idx, 1);
assert_equal_int64_t(cb_log[0].integer_value, 123);
test_char_by_char(buf, strlen(buf));
/* Zero */
buf = ":0\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
assert(res != NULL);
assert_equal_size_t(cb_log_idx, 1);
assert_equal_int64_t(cb_log[0].integer_value, 0);
test_char_by_char(buf, strlen(buf));
/* Signed zero, positive */
buf = ":+0\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 2);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_INVALID_INT);
/* Signed zero, negative */
buf = ":-0\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 2);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_INVALID_INT);
/* Start with 0 */
buf = ":0123\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 2);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_EXPECTED_CR);
/* Start with non-digit */
buf = ":x123\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 1);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_INVALID_INT);
/* Non-digit in the middle */
buf = ":12x3\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 3);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_INVALID_INT);
/* Non-digit at the end */
buf = ":123x\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 4);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_INVALID_INT);
/* Signed 64-bit maximum */
buf = ":9223372036854775807\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
assert(res != NULL);
assert_equal_size_t(cb_log_idx, 1);
assert_equal_int64_t(cb_log[0].integer_value, INT64_MAX);
test_char_by_char(buf, strlen(buf));
/* Signed 64-bit maximum overflow */
buf = ":9223372036854775808\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf)-3);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_OVERFLOW);
/* Signed 64-bit minimum */
buf = ":-9223372036854775808\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
assert(res != NULL);
assert_equal_size_t(cb_log_idx, 1);
assert_equal_int64_t(cb_log[0].integer_value, INT64_MIN);
test_char_by_char(buf, strlen(buf));
/* Signed 64-bit minimum overflow (or underflow...) */
buf = ":-9223372036854775809\r\n";
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf)-3);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_OVERFLOW);
}
TEST(nil) {
SETUP(p);
const char *buf = "$-1\r\n";
size_t len = 5;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_NIL);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 5);
assert_equal_size_t(res->coff, 0);
assert_equal_size_t(res->clen, 0);
/* Check callbacks */
assert_equal_size_t(cb_log_idx, 1);
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(status) {
SETUP(p);
const char *buf = "+status\r\n";
size_t len = 9;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_STATUS);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 9);
assert_equal_size_t(res->coff, 1);
assert_equal_size_t(res->clen, 6);
assert(!strncmp(cb_log[0].string_buf, buf+1, 6));
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(error) {
SETUP(p);
const char *buf = "-error\r\n";
size_t len = 8;
redis_protocol *res;
/* Parse and check resulting protocol_t */
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
assert(res != NULL);
assert_equal_size_t(res->type, REDIS_ERROR);
assert_equal_size_t(res->poff, 0);
assert_equal_size_t(res->plen, 8);
assert_equal_size_t(res->coff, 1);
assert_equal_size_t(res->clen, 5);
assert(!strncmp(cb_log[0].string_buf, buf+1, 5));
/* Chunked check */
test_char_by_char(buf, len);
}
TEST(abort_after_error) {
SETUP(p);
redis_protocol *res;
enum redis_parser_errno err;
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r", 4), 4);
assert(res == NULL);
assert_equal_size_t(redis_parser_execute(p, &res, "\r", 1), 0);
assert(res == NULL);
/* Test if the error matches what we expect */
err = redis_parser_err(p);
assert(err == RPE_EXPECTED_LF);
assert(strcmp("expected \\n", redis_parser_strerror(err)) == 0);
/* Test that the parser doesn't continue after an error */
assert_equal_size_t(redis_parser_execute(p, &res, "\n", 1), 0);
assert(res == NULL);
}
TEST(destroy_callback_after_success) {
SETUP(p);
redis_protocol *res;
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r\n", 5), 5);
assert(res != NULL);
assert(redis_parser_err(p) == RPE_OK);
/* Test that the destroy callback is NOT called when there are
* no errors or the parser is in-flight */
redis_parser_destroy(p);
assert_equal_int(destroy_called, 0);
}
TEST(destroy_callback_after_error) {
SETUP(p);
redis_protocol *res;
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r\r", 5), 4);
assert(res == NULL);
assert(redis_parser_err(p) != RPE_OK);
/* Test that the destroy callback is called after an error */
redis_parser_destroy(p);
assert_equal_int(destroy_called, 1);
}
TEST(destroy_callback_in_flight) {
SETUP(p);
redis_protocol *res;
RESET_PARSER(p);
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r", 4), 4);
assert(res == NULL);
assert(redis_parser_err(p) == RPE_OK);
/* Test that the destroy callback is called when the parser is in-flight */
redis_parser_destroy(p);
assert_equal_int(destroy_called, 1);
}
int main(int argc, char **argv) {
((void)argc);
((void)argv);
test_struct_size();
test_string();
test_empty_string();
test_nil_string();
test_array();
test_empty_array();
test_nil_array();
test_integer();
test_nil();
test_status();
test_error();
test_abort_after_error();
test_destroy_callback_after_success();
test_destroy_callback_after_error();
test_destroy_callback_in_flight();
return 0;
}

572
test/test-request.c Normal file
View File

@ -0,0 +1,572 @@
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "request.h"
#include "test-helper.h"
static struct request_to_write_cb_s {
int callc;
struct {
redis_request_queue *q;
redis_request *r;
} *callv;
} request_to_write_cb_calls = { .callc = 0, .callv = NULL };
void request_to_write_cb_reset(void) {
struct request_to_write_cb_s *s = &request_to_write_cb_calls;
if (s->callc) {
free(s->callv);
}
s->callc = 0;
s->callv = NULL;
}
void request_to_write_cb(redis_request_queue *q, redis_request *r) {
struct request_to_write_cb_s *s = &request_to_write_cb_calls;
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
assert(s->callv != NULL);
s->callv[s->callc].q = q;
s->callv[s->callc].r = r;
s->callc++;
}
static struct request_wait_write_cb_s {
int callc;
struct {
redis_request_queue *q;
redis_request *r;
} *callv;
} request_wait_write_cb_calls = { .callc = 0, .callv = NULL };
void request_wait_write_cb_reset(void) {
struct request_wait_write_cb_s *s = &request_wait_write_cb_calls;
if (s->callc) {
free(s->callv);
}
s->callc = 0;
s->callv = NULL;
}
void request_wait_write_cb(redis_request_queue *q, redis_request *r) {
struct request_wait_write_cb_s *s = &request_wait_write_cb_calls;
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
assert(s->callv != NULL);
s->callv[s->callc].q = q;
s->callv[s->callc].r = r;
s->callc++;
}
static struct request_wait_read_cb_s {
int callc;
struct {
redis_request_queue *q;
redis_request *r;
} *callv;
} request_wait_read_cb_calls = { .callc = 0, .callv = NULL };
void request_wait_read_cb_reset(void) {
struct request_wait_read_cb_s *s = &request_wait_read_cb_calls;
if (s->callc) {
free(s->callv);
}
s->callc = 0;
s->callv = NULL;
}
void request_wait_read_cb(redis_request_queue *q, redis_request *r) {
struct request_wait_read_cb_s *s = &request_wait_read_cb_calls;
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
assert(s->callv != NULL);
s->callv[s->callc].q = q;
s->callv[s->callc].r = r;
s->callc++;
}
static struct write_fn_s {
int callc;
struct {
redis_request_queue *q;
} *callv;
} write_fn_calls = { .callc = 0, .callv = NULL };
void write_fn_reset(void) {
struct write_fn_s *s = &write_fn_calls;
if (s->callc) {
free(s->callv);
}
s->callc = 0;
s->callv = NULL;
}
void write_fn(redis_request_queue *q) {
struct write_fn_s *s = &write_fn_calls;
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
assert(s->callv != NULL);
s->callv[s->callc].q = q;
s->callc++;
}
#define SETUP() \
redis_request_queue q; \
int rv; \
\
request_to_write_cb_reset(); \
request_wait_write_cb_reset(); \
request_wait_read_cb_reset(); \
write_fn_reset(); \
\
rv = redis_request_queue_init(&q); \
assert_equal_return(rv, REDIS_OK); \
\
q.request_to_write_cb = request_to_write_cb; \
q.request_wait_write_cb = request_wait_write_cb; \
q.request_wait_read_cb = request_wait_read_cb; \
q.write_fn = write_fn;
#define TEARDOWN() \
redis_request_queue_destroy(&q);
typedef struct t1_redis_request_s t1_redis_request;
struct t1_redis_request_s {
REDIS_REQUEST_COMMON
const char *buf;
size_t len;
size_t emit;
size_t nemitted;
size_t nwritten;
const char *read_raw_buf;
size_t read_raw_len;
redis_protocol *reply;
int free_calls;
};
void t1_write_ptr(redis_request *_self, const char **buf, size_t *len, int *done) {
t1_redis_request *self = (t1_redis_request*)_self;
size_t to_emit;
assert(self->nemitted <= self->len);
to_emit = self->len - self->nemitted;
if (to_emit > self->emit) {
to_emit = self->emit;
}
*buf = self->buf + self->nemitted;
*len = to_emit;
self->nemitted += to_emit;
if (self->nemitted == self->len) {
*done = 1;
}
}
void t1_write_cb(redis_request *_self, int n) {
t1_redis_request *self = (t1_redis_request*)_self;
self->nwritten += n;
}
void t1_read_cb(redis_request *_self, redis_protocol *p, const char *buf, size_t len, int *done) {
t1_redis_request *self = (t1_redis_request*)_self;
if (p != NULL) {
self->reply = p;
*done = 1;
}
self->read_raw_buf = buf;
self->read_raw_len = len;
}
void t1_free(redis_request *_self) {
t1_redis_request *self = (t1_redis_request*)_self;
self->free_calls++;
}
void t1_init(t1_redis_request *self) {
memset(self, 0, sizeof(*self));
redis_request_init((redis_request*)self);
self->write_ptr = t1_write_ptr;
self->write_cb = t1_write_cb;
self->read_cb = t1_read_cb;
self->free = t1_free;
}
TEST(insert_request) {
SETUP();
t1_redis_request req;
t1_init(&req);
req.buf = "hello";
req.len = strlen(req.buf);
req.emit = 2;
redis_request_queue_insert(&q, (redis_request*)&req);
/* Test that callback was correctly triggered */
assert_equal_int(request_to_write_cb_calls.callc, 1);
assert(request_to_write_cb_calls.callv[0].q == &q);
assert(request_to_write_cb_calls.callv[0].r == (redis_request*)&req);
TEARDOWN();
}
#define INIT_REQUEST(_var, _str) \
t1_redis_request (_var); \
t1_init(&(_var)); \
(_var).buf = (_str); \
(_var).len = strlen((_var).buf); \
(_var).emit = (_var).len;
#define SETUP_INSERTED(_var1, _var2) \
SETUP(); \
INIT_REQUEST(req1, (_var1)); \
INIT_REQUEST(req2, (_var2)); \
redis_request_queue_insert(&q, (redis_request*)&req1); \
redis_request_queue_insert(&q, (redis_request*)&req2); \
TEST(write_ptr) {
SETUP_INSERTED("hello", "world");
const char *buf;
size_t len;
req1.emit = 3;
req2.emit = 3;
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 3);
assert(strncmp(buf, "hel", len) == 0);
assert(req1.write_ptr_done == 0);
/* The first request should have moved to the wait_write queue now */
assert_equal_int(request_wait_write_cb_calls.callc, 1);
assert(request_wait_write_cb_calls.callv[0].q == &q);
assert(request_wait_write_cb_calls.callv[0].r == (redis_request*)&req1);
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 2);
assert(strncmp(buf, "lo", len) == 0);
assert(req1.write_ptr_done == 1);
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 3);
assert(strncmp(buf, "wor", len) == 0);
assert(req2.write_ptr_done == 0);
/* The second request should have moved to the wait_write queue now */
assert_equal_int(request_wait_write_cb_calls.callc, 2);
assert(request_wait_write_cb_calls.callv[1].q == &q);
assert(request_wait_write_cb_calls.callv[1].r == (redis_request*)&req2);
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 2);
assert(strncmp(buf, "ld", len) == 0);
assert(req2.write_ptr_done == 1);
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, -1);
TEARDOWN();
}
TEST(write_ptr_skip_empty) {
SETUP_INSERTED("", "hello");
const char *buf;
size_t len;
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 5);
assert(strncmp(buf, "hello", len) == 0);
/* Both req1 and req2 should be marked as done */
assert(req1.write_ptr_done == 1);
assert(req2.write_ptr_done == 1);
TEARDOWN();
}
#define SETUP_DRAIN_WRITE_PTR() \
do { \
const char *buf; \
size_t len; \
rv = redis_request_queue_write_ptr(&q, &buf, &len); \
} while (rv == 0);
#define SETUP_WRITTEN_UNCONFIRMED() \
SETUP_INSERTED("hello", "world"); \
SETUP_DRAIN_WRITE_PTR();
TEST(write_cb) {
SETUP_WRITTEN_UNCONFIRMED();
/* Assume 3 bytes were written ("hel" in req1) */
rv = redis_request_queue_write_cb(&q, 3);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 3);
assert_equal_size_t(req2.nwritten, 0);
assert(req1.write_cb_done == 0);
assert(req2.write_cb_done == 0);
/* The first request should have moved to the wait_read queue now */
assert_equal_int(request_wait_read_cb_calls.callc, 1);
assert(request_wait_read_cb_calls.callv[0].q == &q);
assert(request_wait_read_cb_calls.callv[0].r == (redis_request*)&req1);
/* Assume another 3 bytes were written ("lo" in req1, "w" in req2) */
rv = redis_request_queue_write_cb(&q, 3);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 5);
assert_equal_size_t(req2.nwritten, 1);
assert(req1.write_cb_done == 1);
assert(req2.write_cb_done == 0);
/* The second request should have moved to the wait_read queue now */
assert_equal_int(request_wait_read_cb_calls.callc, 2);
assert(request_wait_read_cb_calls.callv[1].q == &q);
assert(request_wait_read_cb_calls.callv[1].r == (redis_request*)&req2);
/* Run callback for remaining bytes */
rv = redis_request_queue_write_cb(&q, 4);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 5);
assert_equal_size_t(req2.nwritten, 5);
assert(req1.write_cb_done == 1);
assert(req2.write_cb_done == 1);
/* More bytes cannot be mapped to requests... */
rv = redis_request_queue_write_cb(&q, 4);
assert_equal_size_t(rv, -1);
TEARDOWN();
}
TEST(write_cb_skip_empty) {
SETUP_INSERTED("", "hello");
SETUP_DRAIN_WRITE_PTR();
/* Assume 1 byte was written ("h") */
rv = redis_request_queue_write_cb(&q, 1);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 0);
assert_equal_size_t(req2.nwritten, 1);
assert(req1.write_cb_done == 1);
assert(req2.write_cb_done == 0);
/* Both requests should have moved to the wait_read queue now */
assert_equal_int(request_wait_read_cb_calls.callc, 2);
assert(request_wait_read_cb_calls.callv[0].q == &q);
assert(request_wait_read_cb_calls.callv[0].r == (redis_request*)&req1);
assert(request_wait_read_cb_calls.callv[1].q == &q);
assert(request_wait_read_cb_calls.callv[1].r == (redis_request*)&req2);
/* Run callback for remaining bytes */
rv = redis_request_queue_write_cb(&q, 4);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 0);
assert_equal_size_t(req2.nwritten, 5);
assert(req1.write_cb_done == 1);
assert(req2.write_cb_done == 1);
/* More bytes cannot be mapped to requests... */
rv = redis_request_queue_write_cb(&q, 4);
assert_equal_size_t(rv, -1);
TEARDOWN();
}
#define SETUP_WRITTEN_CONFIRMED() \
SETUP_WRITTEN_UNCONFIRMED(); \
rv = redis_request_queue_write_cb(&q, req1.len); \
assert_equal_size_t(rv, 0); \
rv = redis_request_queue_write_cb(&q, req2.len); \
assert_equal_size_t(rv, 0);
TEST(read_cb) {
SETUP_WRITTEN_CONFIRMED();
/* Feed part of the reply for request 1 */
rv = redis_request_queue_read_cb(&q, "+stat", 5);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.read_raw_len, 5);
assert(strncmp(req1.read_raw_buf, "+stat", req1.read_raw_len) == 0);
assert(req1.reply == NULL);
assert(req1.free_calls == 0);
assert(req1.read_cb_done == 0);
/* Feed remaining part for request 1, and first part for request 2 */
rv = redis_request_queue_read_cb(&q, "us\r\n+st", 7);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.read_raw_len, 4);
assert(strncmp(req1.read_raw_buf, "us\r\n", req1.read_raw_len) == 0);
assert(req1.reply != NULL && req1.reply->type == REDIS_STATUS);
assert(req1.free_calls == 1);
assert(req1.read_cb_done == 1);
assert_equal_size_t(req2.read_raw_len, 3);
assert(strncmp(req2.read_raw_buf, "+st", req2.read_raw_len) == 0);
assert(req2.reply == NULL);
assert(req2.free_calls == 0);
assert(req2.read_cb_done == 0);
/* Feed remaining part for request 2 */
rv = redis_request_queue_read_cb(&q, "atus\r\n", 6);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req2.read_raw_len, 6);
assert(strncmp(req2.read_raw_buf, "atus\r\n", req2.read_raw_len) == 0);
assert(req2.reply != NULL && req1.reply->type == REDIS_STATUS);
assert(req2.free_calls == 1);
assert(req2.read_cb_done == 1);
TEARDOWN();
}
TEST(read_cb_with_parse_error) {
SETUP_WRITTEN_CONFIRMED();
/* Feed part of an erroneous reply */
rv = redis_request_queue_read_cb(&q, "+x\r\r", 4);
assert_equal_return(rv, REDIS_EPARSER);
/* This should keep failing */
rv = redis_request_queue_read_cb(&q, "\n", 1);
assert_equal_return(rv, REDIS_EPARSER);
TEARDOWN();
}
TEST(read_cb_with_pending_write_ptr_emit) {
SETUP_INSERTED("hello", "world");
const char *buf;
size_t len;
req1.emit = 3;
/* Grab first 3 bytes from request */
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 3);
assert(strncmp(buf, "hel", len) == 0);
/* Assume 3 bytes were written ("hel" in req1) */
rv = redis_request_queue_write_cb(&q, 3);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 3);
/* Feed part of the reply for request 1 */
rv = redis_request_queue_read_cb(&q, "+stat", 5);
assert_equal_size_t(rv, 0);
/* Grab remaining bytes from request */
rv = redis_request_queue_write_ptr(&q, &buf, &len);
assert_equal_size_t(rv, 0);
assert_equal_size_t(len, 2);
assert(strncmp(buf, "lo", len) == 0);
/* Assume remaining bytes were written */
rv = redis_request_queue_write_cb(&q, 2);
assert_equal_size_t(rv, 0);
assert_equal_size_t(req1.nwritten, 5);
/* Feed remaining part of the reply for request 1 */
rv = redis_request_queue_read_cb(&q, "us\r\n", 4);
assert_equal_size_t(rv, 0);
assert(req1.reply != NULL && req1.reply->type == REDIS_STATUS);
assert(req1.free_calls == 1);
TEARDOWN();
}
TEST(write_fn_call_on_first_insert) {
SETUP();
INIT_REQUEST(req, "hello");
redis_request_queue_insert(&q, (redis_request*)&req);
/* Test that the write fn was called */
assert_equal_int(write_fn_calls.callc, 1);
assert(write_fn_calls.callv[0].q == &q);
TEARDOWN();
}
TEST(write_fn_no_call_on_second_insert) {
SETUP();
INIT_REQUEST(req1, "hello");
INIT_REQUEST(req2, "world");
redis_request_queue_insert(&q, (redis_request*)&req1);
redis_request_queue_insert(&q, (redis_request*)&req2);
/* Test that the write fn wasn't called twice */
assert_equal_int(write_fn_calls.callc, 1);
assert(write_fn_calls.callv[0].q == &q);
TEARDOWN();
}
TEST(write_fn_call_after_drain) {
SETUP_WRITTEN_CONFIRMED();
INIT_REQUEST(reqN, "hi");
redis_request_queue_insert(&q, (redis_request*)&reqN);
/* Test that the write fn was called */
assert_equal_int(write_fn_calls.callc, 2);
assert(write_fn_calls.callv[0].q == &q);
TEARDOWN();
}
int main(void) {
test_insert_request();
test_write_ptr();
test_write_ptr_skip_empty();
test_write_cb();
test_write_cb_skip_empty();
test_read_cb();
test_read_cb_with_parse_error();
test_read_cb_with_pending_write_ptr_emit();
test_write_fn_call_on_first_insert();
test_write_fn_no_call_on_second_insert();
test_write_fn_call_after_drain();
}