Commit 2ab6fef0 authored by Oran Agra's avatar Oran Agra
Browse files

Merge origin/unstable into 6.2

parents 2dba1e39 8e83bcd2
#define REDISMODULE_EXPERIMENTAL_API
#define _XOPEN_SOURCE 700
#include "redismodule.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include "assert.h"
#define UNUSED(x) (void)(x)
......@@ -21,7 +21,7 @@ int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
UNUSED(argv);
UNUSED(argc);
RedisModuleBlockedClient *bc = RedisModule_GetBlockedClientHandle(ctx);
assert(RedisModule_BlockedClientMeasureTimeEnd(bc)==REDISMODULE_OK);
RedisModule_BlockedClientMeasureTimeEnd(bc);
return RedisModule_ReplyWithSimpleString(ctx,"Request timedout");
}
......@@ -39,7 +39,7 @@ void *BlockDebug_ThreadMain(void *arg) {
long long delay = (unsigned long)targ[1];
long long enable_time_track = (unsigned long)targ[2];
if (enable_time_track)
assert(RedisModule_BlockedClientMeasureTimeStart(bc)==REDISMODULE_OK);
RedisModule_BlockedClientMeasureTimeStart(bc);
RedisModule_Free(targ);
struct timespec ts;
......@@ -49,18 +49,18 @@ void *BlockDebug_ThreadMain(void *arg) {
int *r = RedisModule_Alloc(sizeof(int));
*r = rand();
if (enable_time_track)
assert(RedisModule_BlockedClientMeasureTimeEnd(bc)==REDISMODULE_OK);
RedisModule_BlockedClientMeasureTimeEnd(bc);
RedisModule_UnblockClient(bc,r);
return NULL;
}
/* The thread entry point that actually executes the blocking part
* of the command BLOCK.DEBUG. */
* of the command BLOCK.DOUBLE_DEBUG. */
void *DoubleBlock_ThreadMain(void *arg) {
void **targ = arg;
RedisModuleBlockedClient *bc = targ[0];
long long delay = (unsigned long)targ[1];
assert(RedisModule_BlockedClientMeasureTimeStart(bc)==REDISMODULE_OK);
RedisModule_BlockedClientMeasureTimeStart(bc);
RedisModule_Free(targ);
struct timespec ts;
ts.tv_sec = delay / 1000;
......@@ -72,7 +72,7 @@ void *DoubleBlock_ThreadMain(void *arg) {
/* call again RedisModule_BlockedClientMeasureTimeStart() and
* RedisModule_BlockedClientMeasureTimeEnd and ensure that the
* total execution time is 2x the delay. */
assert(RedisModule_BlockedClientMeasureTimeStart(bc)==REDISMODULE_OK);
RedisModule_BlockedClientMeasureTimeStart(bc);
nanosleep(&ts, NULL);
RedisModule_BlockedClientMeasureTimeEnd(bc);
......@@ -173,14 +173,13 @@ int HelloBlockNoTracking_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **a
int HelloDoubleBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long long delay;
long long timeout;
if (RedisModule_StringToLongLong(argv[1],&delay) != REDISMODULE_OK) {
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
}
pthread_t tid;
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout);
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,0);
/* Now that we setup a blocking client, we need to pass the control
* to the thread. However we need to pass arguments to the thread:
......
#include "redismodule.h"
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
/* If a string is ":deleted:", the special value for deleted hash fields is
* returned; otherwise the input string is returned. */
static RedisModuleString *value_or_delete(RedisModuleString *s) {
if (!strcasecmp(RedisModule_StringPtrLen(s, NULL), ":delete:"))
return REDISMODULE_HASH_DELETE;
else
return s;
}
/* HASH.SET key flags field1 value1 [field2 value2 ..]
*
* Sets 1-4 fields. Returns the same as RedisModule_HashSet().
* Flags is a string of "nxa" where n = NX, x = XX, a = COUNT_ALL.
* To delete a field, use the value ":delete:".
*/
int hash_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc < 5 || argc % 2 == 0 || argc > 11)
return RedisModule_WrongArity(ctx);
RedisModule_AutoMemory(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
size_t flags_len;
const char *flags_str = RedisModule_StringPtrLen(argv[2], &flags_len);
int flags = REDISMODULE_HASH_NONE;
for (size_t i = 0; i < flags_len; i++) {
switch (flags_str[i]) {
case 'n': flags |= REDISMODULE_HASH_NX; break;
case 'x': flags |= REDISMODULE_HASH_XX; break;
case 'a': flags |= REDISMODULE_HASH_COUNT_ALL; break;
}
}
/* Test some varargs. (In real-world, use a loop and set one at a time.) */
int result;
errno = 0;
if (argc == 5) {
result = RedisModule_HashSet(key, flags,
argv[3], value_or_delete(argv[4]),
NULL);
} else if (argc == 7) {
result = RedisModule_HashSet(key, flags,
argv[3], value_or_delete(argv[4]),
argv[5], value_or_delete(argv[6]),
NULL);
} else if (argc == 9) {
result = RedisModule_HashSet(key, flags,
argv[3], value_or_delete(argv[4]),
argv[5], value_or_delete(argv[6]),
argv[7], value_or_delete(argv[8]),
NULL);
} else if (argc == 11) {
result = RedisModule_HashSet(key, flags,
argv[3], value_or_delete(argv[4]),
argv[5], value_or_delete(argv[6]),
argv[7], value_or_delete(argv[8]),
argv[9], value_or_delete(argv[10]),
NULL);
} else {
return RedisModule_ReplyWithError(ctx, "ERR too many fields");
}
/* Check errno */
if (result == 0) {
if (errno == ENOTSUP)
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
else
RedisModule_Assert(errno == ENOENT);
}
return RedisModule_ReplyWithLongLong(ctx, result);
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "hash", 1, REDISMODULE_APIVER_1) ==
REDISMODULE_OK &&
RedisModule_CreateCommand(ctx, "hash.set", hash_set, "",
1, 1, 1) == REDISMODULE_OK) {
return REDISMODULE_OK;
} else {
return REDISMODULE_ERR;
}
}
......@@ -21,6 +21,11 @@ void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
RedisModule_InfoAddFieldLongLong(ctx, "expires", 1);
RedisModule_InfoEndDictField(ctx);
RedisModule_InfoAddSection(ctx, "unsafe");
RedisModule_InfoBeginDictField(ctx, "unsafe:field");
RedisModule_InfoAddFieldLongLong(ctx, "value", 1);
RedisModule_InfoEndDictField(ctx);
if (for_crash_report) {
RedisModule_InfoAddSection(ctx, "Klingon");
RedisModule_InfoAddFieldCString(ctx, "one", "wa’");
......
#include "redismodule.h"
/* ZSET.REM key element
*
* Removes an occurrence of an element from a sorted set. Replies with the
* number of removed elements (0 or 1).
*/
int zset_rem(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 3) return RedisModule_WrongArity(ctx);
RedisModule_AutoMemory(ctx);
int keymode = REDISMODULE_READ | REDISMODULE_WRITE;
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode);
int deleted;
if (RedisModule_ZsetRem(key, argv[2], &deleted) == REDISMODULE_OK)
return RedisModule_ReplyWithLongLong(ctx, deleted);
else
return RedisModule_ReplyWithError(ctx, "ERR ZsetRem failed");
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "zset", 1, REDISMODULE_APIVER_1) ==
REDISMODULE_OK &&
RedisModule_CreateCommand(ctx, "zset.rem", zset_rem, "",
1, 1, 1) == REDISMODULE_OK)
return REDISMODULE_OK;
else
return REDISMODULE_ERR;
}
......@@ -10,6 +10,9 @@ set ::tlsdir "../../tls"
proc main {} {
parse_options
if {$::leaked_fds_file != ""} {
set ::env(LEAKED_FDS_FILE) $::leaked_fds_file
}
spawn_instance sentinel $::sentinel_base_port $::instances_count [list "sentinel deny-scripts-reconfig no"] "../tests/includes/sentinel.conf"
spawn_instance redis $::redis_base_port $::instances_count
run_tests
......
#!/usr/bin/env tclsh
#
# This script detects file descriptors that have leaked from a parent process.
#
# Our goal is to detect file descriptors that were opened by the parent and
# not cleaned up prior to exec(), but not file descriptors that were inherited
# from the grandparent which the parent knows nothing about. To do that, we
# look up every potential leak and try to match it against open files by the
# grandparent process.
# Get PID of parent process
proc get_parent_pid {_pid} {
set fd [open "/proc/$_pid/status" "r"]
set content [read $fd]
close $fd
if {[regexp {\nPPid:\s+(\d+)} $content _ ppid]} {
return $ppid
}
error "failed to get parent pid"
}
# Read symlink to get info about the specified fd of the specified process.
# The result can be the file name or an arbitrary string that identifies it.
# When not able to read, an empty string is returned.
proc get_fdlink {_pid fd} {
if { [catch {set fdlink [file readlink "/proc/$_pid/fd/$fd"]} err] } {
return ""
}
return $fdlink
}
# Linux only
set os [exec uname]
if {$os != "Linux"} {
puts "Only Linux is supported."
exit 0
}
if {![info exists env(LEAKED_FDS_FILE)]} {
puts "Missing LEAKED_FDS_FILE environment variable."
exit 0
}
set outfile $::env(LEAKED_FDS_FILE)
set parent_pid [get_parent_pid [pid]]
set grandparent_pid [get_parent_pid $parent_pid]
set leaked_fds {}
# Look for fds that were directly inherited from our parent but not from
# our grandparent (tcl)
foreach fd [glob -tails -directory "/proc/self/fd" *] {
# Ignore stdin/stdout/stderr
if {$fd == 0 || $fd == 1 || $fd == 2} {
continue
}
set fdlink [get_fdlink "self" $fd]
if {$fdlink == ""} {
continue
}
# We ignore fds that existed in the grandparent, or fds that don't exist
# in our parent (Sentinel process).
if {[get_fdlink $grandparent_pid $fd] == $fdlink ||
[get_fdlink $parent_pid $fd] != $fdlink} {
continue
}
lappend leaked_fds [list $fd $fdlink]
}
# Produce report only if we found leaks
if {[llength $leaked_fds] > 0} {
set fd [open $outfile "w"]
puts $fd [join $leaked_fds "\n"]
close $fd
}
......@@ -41,8 +41,10 @@ test "(init) Sentinels can start monitoring a master" {
S $id SENTINEL SET mymaster down-after-milliseconds 2000
S $id SENTINEL SET mymaster failover-timeout 20000
S $id SENTINEL SET mymaster parallel-syncs 10
S $id SENTINEL SET mymaster notification-script ../../tests/includes/notify.sh
S $id SENTINEL SET mymaster client-reconfig-script ../../tests/includes/notify.sh
if {$::leaked_fds_file != "" && [exec uname] == "Linux"} {
S $id SENTINEL SET mymaster notification-script ../../tests/helpers/check_leaked_fds.tcl
S $id SENTINEL SET mymaster client-reconfig-script ../../tests/helpers/check_leaked_fds.tcl
}
}
}
......
#!/usr/bin/env bash
OS=`uname -s`
if [ ${OS} != "Linux" ]
then
exit 0
fi
# fd 3 is meant to catch the actual access to /proc/pid/fd,
# in case there's an fd leak by the sentinel,
# it can take 3, but then the access to /proc will take another fd, and we'll catch that.
leaked_fd_count=`ls /proc/self/fd | grep -vE '^[0|1|2|3]$' | wc -l`
if [ $leaked_fd_count -gt 0 ]
then
sentinel_fd_leaks_file="../sentinel_fd_leaks"
if [ ! -f $sentinel_fd_leaks_file ]
then
ls -l /proc/self/fd | cat >> $sentinel_fd_leaks_file
lsof -p $$ | cat >> $sentinel_fd_leaks_file
fi
fi
......@@ -248,6 +248,7 @@ proc ::redis::redis_read_reply {id fd} {
- {return -code error [redis_read_line $fd]}
$ {redis_bulk_read $fd}
> -
~ -
* {redis_multi_bulk_read $id $fd}
% {redis_read_map $id $fd}
default {
......
......@@ -259,6 +259,13 @@ proc wait_server_started {config_file stdout pid} {
return $port_busy
}
proc dump_server_log {srv} {
set pid [dict get $srv "pid"]
puts "\n===== Start of server log (pid $pid) =====\n"
puts [exec cat [dict get $srv "stdout"]]
puts "===== End of server log (pid $pid) =====\n"
}
proc start_server {options {code undefined}} {
# setup defaults
set baseconfig "default.conf"
......@@ -492,6 +499,9 @@ proc start_server {options {code undefined}} {
# connect client (after server dict is put on the stack)
reconnect
# remember previous num_failed to catch new errors
set prev_num_failed $::num_failed
# execute provided block
set num_tests $::num_tests
if {[catch { uplevel 1 $code } error]} {
......@@ -529,6 +539,10 @@ proc start_server {options {code undefined}} {
# Re-raise, let handler up the stack take care of this.
error $error $backtrace
}
} else {
if {$::dump_logs && $prev_num_failed != $::num_failed} {
dump_server_log $srv
}
}
# fetch srv back from the server list, in case it was restarted by restart_server (new PID)
......
......@@ -681,3 +681,21 @@ proc string2printable s {
set res "\"$res\""
return $res
}
# Check that probability of each element are between {min_prop} and {max_prop}.
proc check_histogram_distribution {res min_prop max_prop} {
unset -nocomplain mydict
foreach key $res {
dict incr mydict $key 1
}
foreach key [dict keys $mydict] {
set value [dict get $mydict $key]
set probability [expr {double($value) / [llength $res]}]
if {$probability < $min_prop || $probability > $max_prop} {
return false
}
}
return true
}
......@@ -110,6 +110,7 @@ set ::active_servers {} ; # Pids of active Redis instances.
set ::dont_clean 0
set ::wait_server 0
set ::stop_on_failure 0
set ::dump_logs 0
set ::loop 0
set ::tlsdir "tests/tls"
......@@ -555,6 +556,7 @@ proc print_help_screen {} {
"--stop Blocks once the first test fails."
"--loop Execute the specified set of tests forever."
"--wait-server Wait after server is started (so that you can attach a debugger)."
"--dump-logs Dump server log on test failure."
"--tls Run tests in TLS mode."
"--host <addr> Run tests against an external host."
"--port <port> TCP port to use against external host."
......@@ -657,6 +659,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
set ::no_latency 1
} elseif {$opt eq {--wait-server}} {
set ::wait_server 1
} elseif {$opt eq {--dump-logs}} {
set ::dump_logs 1
} elseif {$opt eq {--stop}} {
set ::stop_on_failure 1
} elseif {$opt eq {--loop}} {
......
......@@ -217,6 +217,25 @@ start_server {tags {"acl"}} {
set e
} {*NOPERM*}
test {ACLs set can include subcommands, if already full command exists} {
r ACL setuser bob +memory|doctor
set cmdstr [dict get [r ACL getuser bob] commands]
assert_equal {-@all +memory|doctor} $cmdstr
# Validate the commands have got engulfed to +memory.
r ACL setuser bob +memory
set cmdstr [dict get [r ACL getuser bob] commands]
assert_equal {-@all +memory} $cmdstr
# Appending to the existing access string of bob.
r ACL setuser bob +@all +client|id
# Validate the new commands has got engulfed to +@all.
set cmdstr [dict get [r ACL getuser bob] commands]
assert_equal {+@all} $cmdstr
r CLIENT ID; # Should not fail
r MEMORY DOCTOR; # Should not fail
}
# Note that the order of the generated ACL rules is not stable in Redis
# so we need to match the different parts and not as a whole string.
test {ACL GETUSER is able to translate back command permissions} {
......
......@@ -209,6 +209,58 @@ start_server {tags {"expire"}} {
set e
} {*not an integer*}
test {SET with EX with big integer should report an error} {
catch {r set foo bar EX 10000000000000000} e
set e
} {ERR invalid expire time in set}
test {SET with EX with smallest integer should report an error} {
catch {r SET foo bar EX -9999999999999999} e
set e
} {ERR invalid expire time in set}
test {GETEX with big integer should report an error} {
r set foo bar
catch {r GETEX foo EX 10000000000000000} e
set e
} {ERR invalid expire time in getex}
test {GETEX with smallest integer should report an error} {
r set foo bar
catch {r GETEX foo EX -9999999999999999} e
set e
} {ERR invalid expire time in getex}
test {EXPIRE with big integer overflows when converted to milliseconds} {
r set foo bar
catch {r EXPIRE foo 10000000000000000} e
set e
} {ERR invalid expire time in expire}
test {PEXPIRE with big integer overflow when basetime is added} {
r set foo bar
catch {r PEXPIRE foo 9223372036854770000} e
set e
} {ERR invalid expire time in pexpire}
test {EXPIRE with big negative integer} {
r set foo bar
catch {r EXPIRE foo -9999999999999999} e
assert_match {ERR invalid expire time in expire} $e
r ttl foo
} {-1}
test {PEXPIREAT with big integer works} {
r set foo bar
r PEXPIREAT foo 9223372036854770000
} {1}
test {PEXPIREAT with big negative integer works} {
r set foo bar
r PEXPIREAT foo -9223372036854770000
r ttl foo
} {-2}
test {EXPIRE and SET/GETEX EX/PX/EXAT/PXAT option, TTL should not be reset after loadaof} {
# This test makes sure that expire times are propagated as absolute
# times to the AOF file and not as relative time, so that when the AOF
......
# Helper functions to simulate search-in-radius in the Tcl side in order to
# verify the Redis implementation with a fuzzy test.
proc geo_degrad deg {expr {$deg*atan(1)*8/360}}
proc geo_degrad deg {expr {$deg*(atan(1)*8/360)}}
proc geo_raddeg rad {expr {$rad/(atan(1)*8/360)}}
proc geo_distance {lon1d lat1d lon2d lat2d} {
set lon1r [geo_degrad $lon1d]
......@@ -42,6 +43,34 @@ proc compare_lists {List1 List2} {
return $DiffList
}
# return true If a point in circle.
# search_lon and search_lat define the center of the circle,
# and lon, lat define the point being searched.
proc pointInCircle {radius_km lon lat search_lon search_lat} {
set radius_m [expr {$radius_km*1000}]
set distance [geo_distance $lon $lat $search_lon $search_lat]
if {$distance < $radius_m} {
return true
}
return false
}
# return true If a point in rectangle.
# search_lon and search_lat define the center of the rectangle,
# and lon, lat define the point being searched.
# error: can adjust the width and height of the rectangle according to the error
proc pointInRectangle {width_km height_km lon lat search_lon search_lat error} {
set width_m [expr {$width_km*1000*$error/2}]
set height_m [expr {$height_km*1000*$error/2}]
set lon_distance [geo_distance $lon $lat $search_lon $lat]
set lat_distance [geo_distance $lon $lat $lon $search_lat]
if {$lon_distance > $width_m || $lat_distance > $height_m} {
return false
}
return true
}
# The following list represents sets of random seed, search position
# and radius that caused bugs in the past. It is used by the randomized
# test later as a starting point. When the regression vectors are scanned
......@@ -225,19 +254,26 @@ start_server {tags {"geo"}} {
test {GEOSEARCH non square, long and narrow} {
r del Sicily
r geoadd Sicily 12.75 37.00 "test1"
r geoadd Sicily 12.75 36.995 "test1"
r geoadd Sicily 12.75 36.50 "test2"
r geoadd Sicily 13.00 36.50 "test3"
# box height=2km width=400km
set ret1 [r geosearch Sicily fromlonlat 15 37 bybox 2 400 km]
set ret1 [r geosearch Sicily fromlonlat 15 37 bybox 400 2 km]
assert_equal $ret1 {test1}
# Add a western Hemisphere point
r geoadd Sicily -1 37.00 "test3"
set ret2 [r geosearch Sicily fromlonlat 15 37 bybox 2 3000 km asc]
set ret2 [r geosearch Sicily fromlonlat 15 37 bybox 3000 2 km asc]
assert_equal $ret2 {test1 test3}
}
test {GEOSEARCH corner point test} {
r del Sicily
r geoadd Sicily 12.758489 38.788135 edge1 17.241510 38.788135 edge2 17.250000 35.202000 edge3 12.750000 35.202000 edge4 12.748489955781654 37 edge5 15 38.798135872540925 edge6 17.251510044218346 37 edge7 15 35.201864127459075 edge8 12.692799634687903 38.798135872540925 corner1 12.692799634687903 38.798135872540925 corner2 17.200560937451133 35.201864127459075 corner3 12.799439062548865 35.201864127459075 corner4
set ret [lsort [r geosearch Sicily fromlonlat 15 37 bybox 400 400 km asc]]
assert_equal $ret {edge1 edge2 edge5 edge7}
}
test {GEORADIUSBYMEMBER withdist (sorted)} {
r georadiusbymember nyc "wtc one" 7 km withdist
} {{{wtc one} 0.0000} {{union square} 3.2544} {{central park n/q/r} 6.7000} {4545 6.1975} {{lic market} 6.8969}}
......@@ -360,12 +396,22 @@ start_server {tags {"geo"}} {
assert {[lindex $res 0] eq "Catania"}
}
test {GEOADD + GEORANGE randomized test} {
set attempt 30
test {GEOSEARCH the box spans -180° or 180°} {
r del points
r geoadd points 179.5 36 point1
r geoadd points -179.5 36 point2
assert_equal {point1 point2} [r geosearch points fromlonlat 179 37 bybox 400 400 km asc]
assert_equal {point2 point1} [r geosearch points fromlonlat -179 37 bybox 400 400 km asc]
}
foreach {type} {byradius bybox} {
test "GEOSEARCH fuzzy test - $type" {
if {$::accurate} { set attempt 300 } else { set attempt 30 }
while {[incr attempt -1]} {
set rv [lindex $regression_vectors $rv_idx]
incr rv_idx
set radius_km 0; set width_km 0; set height_km 0
unset -nocomplain debuginfo
set srand_seed [clock milliseconds]
if {$rv ne {}} {set srand_seed [lindex $rv 0]}
......@@ -375,33 +421,55 @@ start_server {tags {"geo"}} {
if {[randomInt 10] == 0} {
# From time to time use very big radiuses
set radius_km [expr {[randomInt 50000]+10}]
if {$type == "byradius"} {
set radius_km [expr {[randomInt 5000]+10}]
} elseif {$type == "bybox"} {
set width_km [expr {[randomInt 5000]+10}]
set height_km [expr {[randomInt 5000]+10}]
}
} else {
# Normally use a few - ~200km radiuses to stress
# test the code the most in edge cases.
set radius_km [expr {[randomInt 200]+10}]
if {$type == "byradius"} {
set radius_km [expr {[randomInt 200]+10}]
} elseif {$type == "bybox"} {
set width_km [expr {[randomInt 200]+10}]
set height_km [expr {[randomInt 200]+10}]
}
}
if {$rv ne {}} {
set radius_km [lindex $rv 1]
set width_km [lindex $rv 1]
set height_km [lindex $rv 1]
}
if {$rv ne {}} {set radius_km [lindex $rv 1]}
set radius_m [expr {$radius_km*1000}]
geo_random_point search_lon search_lat
if {$rv ne {}} {
set search_lon [lindex $rv 2]
set search_lat [lindex $rv 3]
}
lappend debuginfo "Search area: $search_lon,$search_lat $radius_km km"
lappend debuginfo "Search area: $search_lon,$search_lat $radius_km $width_km $height_km km"
set tcl_result {}
set argv {}
for {set j 0} {$j < 20000} {incr j} {
geo_random_point lon lat
lappend argv $lon $lat "place:$j"
set distance [geo_distance $lon $lat $search_lon $search_lat]
if {$distance < $radius_m} {
lappend tcl_result "place:$j"
if {$type == "byradius"} {
if {[pointInCircle $radius_km $lon $lat $search_lon $search_lat]} {
lappend tcl_result "place:$j"
}
} elseif {$type == "bybox"} {
if {[pointInRectangle $width_km $height_km $lon $lat $search_lon $search_lat 1]} {
lappend tcl_result "place:$j"
}
}
lappend debuginfo "place:$j $lon $lat [expr {$distance/1000}] km"
lappend debuginfo "place:$j $lon $lat"
}
r geoadd mypoints {*}$argv
set res [lsort [r georadius mypoints $search_lon $search_lat $radius_km km]]
if {$type == "byradius"} {
set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat byradius $radius_km km]]
} elseif {$type == "bybox"} {
set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_km $height_km km]]
}
set res2 [lsort $tcl_result]
set test_result OK
......@@ -409,18 +477,27 @@ start_server {tags {"geo"}} {
set rounding_errors 0
set diff [compare_lists $res $res2]
foreach place $diff {
lassign [lindex [r geopos mypoints $place] 0] lon lat
set mydist [geo_distance $lon $lat $search_lon $search_lat]
set mydist [expr $mydist/1000]
if {($mydist / $radius_km) > 0.999} {
incr rounding_errors
continue
}
if {$mydist < $radius_m} {
# This is a false positive for redis since given the
# same points the higher precision calculation provided
# by TCL shows the point within range
incr rounding_errors
continue
if {$type == "byradius"} {
if {($mydist / $radius_km) > 0.999} {
incr rounding_errors
continue
}
if {$mydist < [expr {$radius_km*1000}]} {
# This is a false positive for redis since given the
# same points the higher precision calculation provided
# by TCL shows the point within range
incr rounding_errors
continue
}
} elseif {$type == "bybox"} {
# we add 0.1% error for floating point calculation error
if {[pointInRectangle $width_km $height_km $lon $lat $search_lon $search_lat 1.001]} {
incr rounding_errors
continue
}
}
}
......@@ -447,7 +524,6 @@ start_server {tags {"geo"}} {
set mydist [geo_distance $lon $lat $search_lon $search_lat]
set mydist [expr $mydist/1000]
puts "$place -> [r geopos mypoints $place] $mydist $where"
if {($mydist / $radius_km) > 0.999} {incr rounding_errors}
}
set test_result FAIL
}
......@@ -456,4 +532,91 @@ start_server {tags {"geo"}} {
}
set test_result
} {OK}
}
test {GEOSEARCH box edges fuzzy test} {
if {$::accurate} { set attempt 300 } else { set attempt 30 }
while {[incr attempt -1]} {
unset -nocomplain debuginfo
set srand_seed [clock milliseconds]
lappend debuginfo "srand_seed is $srand_seed"
expr {srand($srand_seed)} ; # If you need a reproducible run
r del mypoints
geo_random_point search_lon search_lat
set width_m [expr {[randomInt 10000]+10}]
set height_m [expr {[randomInt 10000]+10}]
set lat_delta [geo_raddeg [expr {$height_m/2/6372797.560856}]]
set long_delta_top [geo_raddeg [expr {$width_m/2/6372797.560856/cos([geo_degrad [expr {$search_lat+$lat_delta}]])}]]
set long_delta_middle [geo_raddeg [expr {$width_m/2/6372797.560856/cos([geo_degrad $search_lat])}]]
set long_delta_bottom [geo_raddeg [expr {$width_m/2/6372797.560856/cos([geo_degrad [expr {$search_lat-$lat_delta}]])}]]
# Total of 8 points are generated, which are located at each vertex and the center of each side
set points(north) [list $search_lon [expr {$search_lat+$lat_delta}]]
set points(south) [list $search_lon [expr {$search_lat-$lat_delta}]]
set points(east) [list [expr {$search_lon+$long_delta_middle}] $search_lat]
set points(west) [list [expr {$search_lon-$long_delta_middle}] $search_lat]
set points(north_east) [list [expr {$search_lon+$long_delta_top}] [expr {$search_lat+$lat_delta}]]
set points(north_west) [list [expr {$search_lon-$long_delta_top}] [expr {$search_lat+$lat_delta}]]
set points(south_east) [list [expr {$search_lon+$long_delta_bottom}] [expr {$search_lat-$lat_delta}]]
set points(south_west) [list [expr {$search_lon-$long_delta_bottom}] [expr {$search_lat-$lat_delta}]]
lappend debuginfo "Search area: geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_m $height_m m"
set tcl_result {}
foreach name [array names points] {
set x [lindex $points($name) 0]
set y [lindex $points($name) 1]
# If longitude crosses -180° or 180°, we need to convert it.
# latitude doesn't have this problem, because it's scope is -70~70, see geo_random_point
if {$x > 180} {
set x [expr {$x-360}]
} elseif {$x < -180} {
set x [expr {$x+360}]
}
r geoadd mypoints $x $y place:$name
lappend tcl_result "place:$name"
lappend debuginfo "geoadd mypoints $x $y place:$name"
}
set res2 [lsort $tcl_result]
# make the box larger by two meter in each direction to put the coordinate slightly inside the box.
set height_new [expr {$height_m+4}]
set width_new [expr {$width_m+4}]
set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]]
if {$res != $res2} {
set diff [compare_lists $res $res2]
lappend debuginfo "res: $res, res2: $res2, diff: $diff"
fail "place should be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new"
}
# The width decreases and the height increases. Only north and south are found
set width_new [expr {$width_m-4}]
set height_new [expr {$height_m+4}]
set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]]
if {$res != {place:north place:south}} {
lappend debuginfo "res: $res"
fail "place should not be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new"
}
# The width increases and the height decreases. Only ease and west are found
set width_new [expr {$width_m+4}]
set height_new [expr {$height_m-4}]
set res [lsort [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]]
if {$res != {place:east place:west}} {
lappend debuginfo "res: $res"
fail "place should not be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new"
}
# make the box smaller by two meter in each direction to put the coordinate slightly outside the box.
set height_new [expr {$height_m-4}]
set width_new [expr {$width_m-4}]
set res [r geosearch mypoints fromlonlat $search_lon $search_lat bybox $width_new $height_new m]
if {$res != ""} {
lappend debuginfo "res: $res"
fail "place should not be found, debuginfo: $debuginfo, height_new: $height_new width_new: $width_new"
}
unset -nocomplain debuginfo
}
}
}
......@@ -150,4 +150,12 @@ start_server {tags {"info"}} {
assert_match {} [errorstat NOPERM]
}
}
start_server {} {
test {Unsafe command names are sanitized in INFO output} {
catch {r host:} e
set info [r info commandstats]
assert_match {*cmdstat_host_:calls=1*} $info
}
}
}
......@@ -8,12 +8,18 @@ start_server {tags {"modules"}} {
test { blocked clients time tracking - check blocked command that uses RedisModule_BlockedClientMeasureTimeStart() is tracking background time} {
r slowlog reset
r config set slowlog-log-slower-than 200000
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r block.debug 0 10000
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r config resetstat
r block.debug 200 10000
assert_equal [r slowlog len] 1
if {!$::no_latency} {
assert_equal [r slowlog len] 1
}
set cmdstatline [cmdrstat block.debug r]
......@@ -25,30 +31,41 @@ start_server {tags {"modules"}} {
test { blocked clients time tracking - check blocked command that uses RedisModule_BlockedClientMeasureTimeStart() is tracking background time even in timeout } {
r slowlog reset
r config set slowlog-log-slower-than 200000
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r block.debug 0 20000
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r config resetstat
r block.debug 20000 200
assert_equal [r slowlog len] 1
r block.debug 20000 500
if {!$::no_latency} {
assert_equal [r slowlog len] 1
}
set cmdstatline [cmdrstat block.debug r]
regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline usec usec_per_call
assert {$usec >= 100000}
assert {$usec_per_call >= 100000}
assert {$usec >= 250000}
assert {$usec_per_call >= 250000}
}
test { blocked clients time tracking - check blocked command with multiple calls RedisModule_BlockedClientMeasureTimeStart() is tracking the total background time } {
r slowlog reset
r config set slowlog-log-slower-than 200000
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r block.double_debug 0
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r config resetstat
r block.double_debug 100
assert_equal [r slowlog len] 1
if {!$::no_latency} {
assert_equal [r slowlog len] 1
}
set cmdstatline [cmdrstat block.double_debug r]
regexp "calls=1,usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline usec usec_per_call
......@@ -59,9 +76,13 @@ start_server {tags {"modules"}} {
test { blocked clients time tracking - check blocked command without calling RedisModule_BlockedClientMeasureTimeStart() is not reporting background time } {
r slowlog reset
r config set slowlog-log-slower-than 200000
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
r block.debug_no_track 200 1000
# ensure slowlog is still empty
assert_equal [r slowlog len] 0
if {!$::no_latency} {
assert_equal [r slowlog len] 0
}
}
}
set testmodule [file normalize tests/modules/hash.so]
start_server {tags {"modules"}} {
r module load $testmodule
test {Module hash set} {
r set k mystring
assert_error "WRONGTYPE*" {r hash.set k "" hello world}
r del k
# "" = count updates and deletes of existing fields only
assert_equal 0 [r hash.set k "" squirrel yes]
# "a" = COUNT_ALL = count inserted, modified and deleted fields
assert_equal 2 [r hash.set k "a" banana no sushi whynot]
# "n" = NX = only add fields not already existing in the hash
# "x" = XX = only replace the value for existing fields
assert_equal 0 [r hash.set k "n" squirrel hoho what nothing]
assert_equal 1 [r hash.set k "na" squirrel hoho something nice]
assert_equal 0 [r hash.set k "xa" new stuff not inserted]
assert_equal 1 [r hash.set k "x" squirrel ofcourse]
assert_equal 1 [r hash.set k "" sushi :delete: none :delete:]
r hgetall k
} {squirrel ofcourse banana no what nothing something nice}
}
......@@ -85,5 +85,10 @@ start_server {tags {"modules"}} {
set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d]
} {3}
test {module info unsafe fields} {
set info [r info infotest_unsafe]
assert_match {*infotest_unsafe_field:value=1*} $info
}
# TODO: test crash report.
}
set testmodule [file normalize tests/modules/zset.so]
start_server {tags {"modules"}} {
r module load $testmodule
test {Module zset rem} {
r del k
r zadd k 100 hello 200 world
assert_equal 1 [r zset.rem k hello]
assert_equal 0 [r zset.rem k hello]
assert_equal 1 [r exists k]
# Check that removing the last element deletes the key
assert_equal 1 [r zset.rem k world]
assert_equal 0 [r exists k]
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment