Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Lots and lots of fixes

Network failure tests

Memcached interop compatibility tests

Conversion/Deconversion settings tested
Timeout settings tested
Network failure behavior tested
Some more build utilities
  • Loading branch information...
commit 94b63d3ed06860d90440d0daa10264ac89f1692d 1 parent 6892af5
@mnunberg authored
View
5 .gitmodules
@@ -1,3 +1,6 @@
[submodule "Config"]
path = Config
- url = file://Config
+ url = git://github.com/mnunberg/perl/Couchbase-Config.git
+[submodule "VBucket"]
+ path = VBucket
+ url = git://github.com/mnunberg/perl-Couchbase-VBucket.git
View
54 Client.xs
@@ -122,11 +122,11 @@ PLCB_connect(SV *self)
return 1;
} else {
if( (err = libcouchbase_connect(instance)) == LIBCOUCHBASE_SUCCESS) {
- object->connected = 1;
libcouchbase_wait(instance);
if(av_len(object->errors) > -1) {
return 0;
}
+ object->connected = 1;
return 1;
} else {
plcb_errstack_push(object, err, NULL);
@@ -239,16 +239,6 @@ static SV *PLCB_get_common(SV *self, SV *key, int exp_offset)
_sync_return_single(object, err, syncp);
}
-SV *PLCB_get_errors(SV *self)
-{
- libcouchbase_t instance;
- PLCB_t *object;
- AV *errors;
-
- mk_instance_vars(self, instance, object);
- return newRV_inc((SV*)object->errors);
-}
-
#define set_plst_get_offset(exp_idx, exp_var, diemsg) \
if(items == (exp_idx - 1)) { \
exp_var = 0; \
@@ -383,7 +373,8 @@ enum {
SETTINGS_ALIAS_SERIALIZE,
SETTINGS_ALIAS_CONVERT,
SETTINGS_ALIAS_DECONVERT,
- SETTINGS_ALIAS_COMP_THRESHOLD
+ SETTINGS_ALIAS_COMP_THRESHOLD,
+ SETTINGS_ALIAS_DEREF_RVPV
};
@@ -574,6 +565,18 @@ PLCB_remove(self, key, ...)
SV *
PLCB_get_errors(self)
SV *self
+
+ PREINIT:
+ libcouchbase_t instance;
+ PLCB_t *object;
+ AV *errors;
+
+ CODE:
+ mk_instance_vars(self, instance, object);
+ RETVAL = newRV_inc((SV*)object->errors);
+
+ OUTPUT:
+ RETVAL
SV *
@@ -609,6 +612,7 @@ PLCB__settings(self, ...)
conversion_settings = SETTINGS_ALIAS_CONVERT
deconversion_settings = SETTINGS_ALIAS_DECONVERT
compress_threshold = SETTINGS_ALIAS_COMP_THRESHOLD
+ dereference_scalar_ref_settings = SETTINGS_ALIAS_DEREF_RVPV
PREINIT:
int flag;
@@ -630,11 +634,14 @@ PLCB__settings(self, ...)
flag = PLCBf_USE_STORABLE|PLCBf_USE_COMPRESSION;
break;
case SETTINGS_ALIAS_DECONVERT:
- flag = PLCBf_NO_DECONVERT;
+ flag = PLCBf_DECONVERT;
break;
case SETTINGS_ALIAS_COMP_THRESHOLD:
flag = PLCBf_COMPRESS_THRESHOLD;
break;
+ case SETTINGS_ALIAS_DEREF_RVPV:
+ flag = PLCBf_DEREF_RVPV;
+ break;
case 0:
die("This function should not be called directly. "
"use one of its aliases");
@@ -643,7 +650,7 @@ PLCB__settings(self, ...)
break;
}
if(items == 2) {
- new_value = sv_2bool(ST(2));
+ new_value = sv_2bool(ST(1));
} else if (items == 1) {
new_value = -1;
} else {
@@ -657,7 +664,8 @@ PLCB__settings(self, ...)
RETVAL = plcb_convert_settings(object, flag, new_value);
-
+ //warn("Report flag %d = %d", flag, RETVAL);
+
OUTPUT:
RETVAL
@@ -682,7 +690,7 @@ PLCB_timeout(self, ...)
if(items == 2) {
new_param = SvNV(ST(1));
- if(!new_param) {
+ if(new_param <= 0) {
warn("Cannot disable timeouts.");
XSRETURN_UNDEF;
}
@@ -699,12 +707,18 @@ int
PLCB_connect(self)
SV *self
-
+
BOOT:
{
-
- /*because xsubpp is stupid, we can't use an inline macro for this*/
-
+ {
+ libcouchbase_uint32_t cbc_version = 0;
+ const char *cbc_version_string;
+ cbc_version_string = libcouchbase_get_version(&cbc_version);
+ /*
+ warn("Couchbase library version is (%s) %x",
+ cbc_version_string, cbc_version);
+ */
+ }
/*Client_multi.xs*/
PUSHMARK(SP);
mXPUSHs(newSVpv("Couchbase::Client",0));
2  Config
@@ -1 +1 @@
-Subproject commit d746ddff0d7e449a86712531ccf8f66f355b57ca
+Subproject commit af3708773ecb53396d1f47d0f4133ab09f2d27a1
View
2  MANIFEST
@@ -3,6 +3,7 @@ MANIFEST
MANIFEST.SKIP
Makefile.PL
README.pod
+PLCB_ConfUtil.pm
lib/Couchbase/Client.pm
lib/Couchbase/Client/Return.pm
@@ -24,6 +25,7 @@ lib/Couchbase/Test/Async.pm
lib/Couchbase/Test/Async/Loop.pm
lib/Couchbase/Test/Settings.pm
lib/Couchbase/Test/Interop.pm
+lib/Couchbase/Test/Netfail.pm
Client.xs
Client_multi.xs
View
18 Makefile.PL
@@ -5,19 +5,7 @@ use ExtUtils::MakeMaker;
use Dir::Self;
use lib __DIR__;
-
-my $plcb_config = do 'PLCB_Config.pm';
-
-my $include_path = $plcb_config->{COUCHBASE_INCLUDE_PATH} || "";
-my $library_path = $plcb_config->{COUCHBASE_LIBRARY_PATH} || "";
-
-if($include_path) {
- $include_path = "-I$include_path";
-}
-
-if($library_path) {
- $library_path = "-L$library_path";
-}
+use PLCB_ConfUtil;
# .c files
my @source_modules = qw(
@@ -77,8 +65,8 @@ WriteMakefile(
NEEDS_LINKING => 1,
OPTIMIZE => '-O0 -ggdb3 -Wdeclaration-after-statement -Werror -std=gnu89',
#CCFLAGS => '-Wdeclaration-after-statement',
- LIBS => ["$library_path -lcouchbase -lcouchbase_libevent -lvbucket"],
- INC => $include_path,
+ LIBS => [PLCB_ConfUtil::get_gcc_linker_flags],
+ INC => PLCB_ConfUtil::get_include_dir,
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Couchbase-Client-*' },
);
View
44 PLCB_ConfUtil.pm
@@ -0,0 +1,44 @@
+package PLCB_ConfUtil;
+use strict;
+use warnings;
+use Dir::Self;
+use lib __DIR__;
+
+my $config = do 'PLCB_Config.pm';
+if(!$config) {
+ warn("Couldn't find PLCB_Config.pm. Assuming defaults");
+ $config = {};
+}
+
+sub set_gcc_env {
+ my $existing_env = $ENV{C_INCLUDE_PATH};
+ $existing_env ||= "";
+ my $new_env = $config->{COUCHBASE_INCLUDE_PATH};
+ if(!$new_env) {
+ return;
+ } else {
+ $ENV{C_INCLUDE_PATH} = "$new_env:$existing_env";
+ }
+}
+
+sub get_gcc_linker_flags {
+ my $libpath = $config->{COUCHBASE_LIBRARY_PATH};
+ if($libpath) {
+ $libpath = "-L$libpath ";
+ } else {
+ $libpath = "";
+ }
+ $libpath .= '-lcouchbase -lcouchbase_libevent -lvbucket';
+ return $libpath;
+}
+
+sub get_include_dir {
+ my $dir = $config->{COUCHBASE_INCLUDE_PATH};
+ if($dir) {
+ return "-I$dir";
+ } else {
+ return "";
+ }
+}
+
+1;
View
8 PLCB_Config.pm
@@ -1,6 +1,7 @@
package PLCB_Config;
use strict;
use warnings;
+
#this perl 'hash' contains configuration information necessary
#to bootstrap and/or configure the perl couchbase client and run
#necessary tests.
@@ -10,9 +11,10 @@ my $params = {
COUCHBASE_LIBRARY_PATH => "/sources/libcouchbase/.libs",
#URL from which to download the mock JAR file for tests
- COUCHBASE_MOCK_JARURL =>
- "http://files.couchbase.com/maven2/org/couchbase/mock/".
- "CouchbaseMock/0.5-SNAPSHOT/CouchbaseMock-0.5-20120202.071818-12.jar",
+ #COUCHBASE_MOCK_JARURL =>
+ # "http://files.couchbase.com/maven2/org/couchbase/mock/".
+ # "CouchbaseMock/0.5-SNAPSHOT/CouchbaseMock-0.5-20120202.071818-12.jar",
+ COUCHBASE_MOCK_JARURL => 'http://files.avsej.net/CouchbaseMock.jar'
};
1  VBucket
@@ -0,0 +1 @@
+Subproject commit 687dcad7959171ca45d740b276311188af2c3a13
View
36 convert.c
@@ -108,6 +108,10 @@ compression_convert(SV *meth, SV *input, int direction)
return converted;
}
+#define plcb_can_convert(object, flags) \
+ ((object->my_flags & (PLCBf_DECONVERT|flags)) == \
+ (PLCBf_DECONVERT|flags))
+
void plcb_convert_storage(
PLCB_t *object, SV **data_sv, STRLEN *data_len,
uint32_t *flags)
@@ -115,16 +119,27 @@ void plcb_convert_storage(
SV *sv;
*flags = 0;
- if(object->my_flags & PLCBf_DO_CONVERSION == 0 && SvROK(*data_sv) == 0) {
+ /* dereference SCALAR reference. bypass all conversion checks because
+ * this is an internal setting
+ */
+ if( (object->my_flags & PLCBf_DEREF_RVPV) &&
+ SvROK(*data_sv) && SvTYPE(SvRV(*data_sv)) == SVt_PV) {
+ *data_sv = SvRV(*data_sv);
+ *data_len = SvCUR(*data_sv);
+ }
+
+ if( (object->my_flags & PLCBf_DO_CONVERSION == 0 ||
+ object->my_flags & PLCBf_DECONVERT == 0)
+ && SvROK(*data_sv) == 0) {
return;
}
sv = *data_sv;
+ /*only serialize references*/
if(SvROK(sv)) {
- if(!(object->my_flags & PLCBf_USE_STORABLE)) {
- die("Serialization not enabled "
- "but we were passed a reference");
+ if(!plcb_can_convert(object, PLCBf_USE_STORABLE)) {
+ croak("serialization requested but output conversion disabled");
}
sv = serialize_convert(object->cv_serialize, sv,
@@ -133,7 +148,7 @@ void plcb_convert_storage(
*data_len = SvCUR(sv); /*set this so compression method sees new length*/
}
- if( (object->my_flags & PLCBf_USE_COMPRESSION)
+ if( plcb_can_convert(object, PLCBf_USE_COMPRESSION)
&& object->compress_threshold
&& *data_len >= object->compress_threshold ) {
@@ -166,7 +181,7 @@ SV* plcb_convert_retrieval(
input_sv = newSVpvn(data, data_len);
if(plcb_storeflags_has_conversion(object, flags) == 0
- || (object->my_flags & PLCBf_NO_DECONVERT) ) {
+ || (object->my_flags & PLCBf_DECONVERT) == 0 ) {
return input_sv;
}
@@ -194,8 +209,6 @@ SV* plcb_convert_retrieval(
int plcb_convert_settings(PLCB_t *object, int flag, int new_value)
{
int ret;
-
-
if(flag == PLCBf_COMPRESS_THRESHOLD) {
/*this isn't really a flag value, but a proper integer*/
ret = object->compress_threshold;
@@ -203,6 +216,9 @@ int plcb_convert_settings(PLCB_t *object, int flag, int new_value)
object->compress_threshold = new_value >= 0
? new_value
: object->compress_threshold;
+ if(new_value >= 0) {
+ object->my_flags |= PLCBf_USE_COMPRESSION;
+ }
return ret;
}
@@ -216,9 +232,5 @@ int plcb_convert_settings(PLCB_t *object, int flag, int new_value)
}
}
- if(flag == PLCBf_NO_DECONVERT && new_value > 0) {
- object->my_flags &= (~ (PLCBf_USE_COMPRESSION|PLCBf_USE_STORABLE));
- }
-
return ret;
}
View
3  ctor.c
@@ -82,7 +82,8 @@ void plcb_ctor_init_common(PLCB_t *object, libcouchbase_t instance,
}
/*gather instance-related options from the constructor*/
- if( (tmpsv = av_fetch(options, PLCB_CTORIDX_TIMEOUT, 0)) ) {
+ if( (tmpsv = av_fetch(options, PLCB_CTORIDX_TIMEOUT, 0)) &&
+ (SvIOK(*tmpsv) || SvNOK(*tmpsv))) {
timeout_value = SvNV(*tmpsv);
if(!timeout_value) {
warn("Cannot use 0 for timeout");
View
4 error_constants.pl
@@ -1,4 +1,6 @@
use ExtUtils::H2PM;
+use PLCB_ConfUtil;
+PLCB_ConfUtil::set_gcc_env();
module "Couchbase::Client::Errors";
use_export;
@@ -28,6 +30,8 @@
UNKNOWN_COMMAND
UNKNOWN_HOST
PROTOCOL_ERROR
+ ETIMEDOUT
+ CONNECT_ERROR
BUCKET_ENOENT
);
foreach my $cbase (@constant_basenames) {
View
8 idx_constants.pl
@@ -1,13 +1,16 @@
use ExtUtils::H2PM;
+use PLCB_ConfUtil;
module "Couchbase::Client::IDXConst";
use_export;
-$ENV{C_INCLUDE_PATH} = './';
+
include "sys/types.h";
include "perl-couchbase.h";
include "perl-couchbase-async.h";
+PLCB_ConfUtil::set_gcc_env();
+
my @const_bases = qw(
CTORIDX_SERVERS
CTORIDX_USERNAME
@@ -38,7 +41,8 @@
USE_STORABLE
USE_CONVERT_UTF8
NO_CONNECT
- NO_DECONVERT
+ DECONVERT
+ DEREF_RVPV
);
constant("PLCBf_$_", name => "f$_") for (@ctor_flags);
View
226 lib/Couchbase/Client.pm
@@ -2,7 +2,7 @@ package Couchbase::Client;
BEGIN {
require XSLoader;
- our $VERSION = '0.01_1';
+ our $VERSION = '0.10_0';
XSLoader::load(__PACKAGE__, $VERSION);
}
@@ -16,7 +16,6 @@ use Couchbase::Client::Return;
my $have_storable = eval "use Storable; 1;";
my $have_zlib = eval "use Compress::Zlib; 1;";
-use Log::Fu;
use Array::Assign;
{
@@ -30,24 +29,43 @@ use Array::Assign;
sub _make_conversion_settings {
my ($arglist,$options) = @_;
- my $compress_threshold = delete $options->{compress_threshold};
my $flags = 0;
- $compress_threshold = 0 unless defined $compress_threshold;
- $compress_threshold = 0 if $compress_threshold < 0;
- $arglist->[CTORIDX_COMP_THRESHOLD] = $compress_threshold;
- my $meth_comp = 0;
- if(exists $options->{compress_methods}) {
- $meth_comp = delete $options->{compress_threshold};
+ $arglist->[CTORIDX_MYFLAGS] ||= 0;
+
+ if($options->{dereference_scalar_ref}) {
+ $arglist->[CTORIDX_MYFLAGS] |= fDEREF_RVPV;
+ }
+
+ if(exists $options->{deconversion}) {
+ if(! delete $options->{deconversion}) {
+ return;
+ }
+ } else {
+ $flags |= fDECONVERT;
+ }
+
+ if(exists $options->{compress_threshold}) {
+ my $compress_threshold = delete $options->{compress_threshold};
+ $compress_threshold =
+ (!$compress_threshold || $compress_threshold < 0)
+ ? 0 : $compress_threshold;
+ $arglist->[CTORIDX_COMP_THRESHOLD] = $compress_threshold;
+ if($compress_threshold) {
+ $flags |= fUSE_COMPRESSION;
+ }
}
- if($meth_comp == 0 && $have_zlib) {
+
+ my $meth_comp;
+ if(exists $options->{compress_methods}) {
+ $meth_comp = delete $options->{compress_methods};
+ } elsif($have_zlib) {
$meth_comp = [ sub { ${$_[1]} = Compress::Zlib::memGzip(${$_[0]}) },
sub { ${$_[1]} = Compress::Zlib::memGunzip(${$_[0]}) }]
}
- if($meth_comp) {
- $flags |= fUSE_COMPRESSION;
+ if(defined $meth_comp) {
$arglist->[CTORIDX_COMP_METHODS] = $meth_comp;
}
@@ -65,24 +83,14 @@ sub _make_conversion_settings {
$arglist->[CTORIDX_SERIALIZE_METHODS] = $meth_serialize;
}
- $arglist->[CTORIDX_MYFLAGS] = $flags;
+ $arglist->[CTORIDX_MYFLAGS] |= $flags;
}
sub _MkCtorIDX {
my $opts = shift;
- my $server;
my @arglist;
- my $servers = delete $opts->{servers};
- if(!$servers) {
- $server = delete $opts->{server};
- } else {
- $server = $servers->[0];
- }
-
- if(!$server) {
- die("Must have server");
- }
+ my $server = delete $opts->{server} or die "Must have server";
arry_assign_i(@arglist,
CTORIDX_SERVERS, $server,
CTORIDX_USERNAME, delete $opts->{username},
@@ -93,8 +101,10 @@ sub _MkCtorIDX {
my $tmp = delete $opts->{io_timeout} ||
delete $opts->{select_timeout} ||
- delete $opts->{connect_timeout};
-
+ delete $opts->{connect_timeout} ||
+ delete $opts->{timeout};
+
+ $tmp ||= 2.5;
$arglist[CTORIDX_TIMEOUT] = $tmp if defined $tmp;
$arglist[CTORIDX_NO_CONNECT] = delete $opts->{no_init_connect};
@@ -106,17 +116,63 @@ sub _MkCtorIDX {
return \@arglist;
}
+my %RETRY_ERRORS = (
+ COUCHBASE_NETWORK_ERROR, 1,
+ COUCHBASE_CONNECT_ERROR, 1,
+ COUCHBASE_ETIMEDOUT, 1
+);
+
sub new {
my ($pkg,$opts) = @_;
- my $arglist = _MkCtorIDX($opts);
+ my $server_list;
+ if($opts->{servers}) {
+ $server_list = delete $opts->{servers};
+ if(ref $server_list ne 'ARRAY') {
+ $server_list = [$server_list];
+ }
+ } elsif ($opts->{server}) {
+ $server_list = [ delete $opts->{server} or die "server is false" ];
+ } else {
+ die("Must have server or servers");
+ }
+
+ my $connected_ok;
+ my $no_init_connect = $opts->{no_init_connect};
+ my $self;
- my $o = $pkg->construct($arglist);
- my $errors = $o->get_errors;
- foreach (@$errors) {
- my ($errno,$errstr) = @$_;
- log_err($errstr);
+ my @all_errors;
+
+ my $privopts;
+ while(!$connected_ok && (my $server = shift @$server_list)) {
+ $opts->{server} = $server;
+ $privopts = {%$opts};
+ my $arglist = _MkCtorIDX($privopts);
+ $self = $pkg->construct($arglist);
+ my $errors = $self->get_errors;
+ my $error_retriable;
+ if(scalar @$errors) {
+ push @all_errors, @$errors;
+ foreach (@$errors) {
+ my ($errno,$errstr) = @$_;
+ warn("(cbc_errno=$errno)");
+ if(exists $RETRY_ERRORS{$errno}) {
+ $error_retriable++;
+ }
+ }
+ if(!$error_retriable) {
+ warn("Didn't find any non-retriable errors");
+ last;
+ }
+ } else {
+ last;
+ }
+ if($no_init_connect) {
+ last;
+ }
}
- return $o;
+ @{$self->get_errors} = @all_errors;
+ return $self;
+
}
#This is called from within C to record our stats:
@@ -222,8 +278,17 @@ C<localhost:8091>.
=item servers
-Takes an arrayref of servers, currently only the first server is used, but this
-will change.
+A list of servers to try, in order. C<Couchbase::Client> will connect to the first
+responsive server (optionally complaining with warnings about failed servers).
+
+This is a special construction-time option. It will not work in conjunction with
+the L</no_init_connect> option.
+
+By virtue of the design of the Couchbase architecture, already-connected clients
+will learn about alternate entry points once an initial entry into the cluster
+has been established. Therefore if a connection fails in-situ, the client is
+likely to know of alternate entry points, and thus the server list is only
+useful for discovering the initial entry point.
=item username, password
@@ -233,6 +298,22 @@ Authentication credentials for the connection. Defaults to NULL
The bucket name for the connection. Defaults to C<default>
+=item io_timeout
+
+=item connect_timeout
+
+=item select_timeout
+
+=item timeout
+
+These all alias to the same setting, and control the time the client waits
+for a response after it sends a request to the server.
+
+The value should be specified in seconds (fractional values are allowed)
+
+Defaults to C<2.5>
+
+
=back
=head4 Conversion Options
@@ -332,7 +413,7 @@ By default we use L<Compress::Zlib|Compress::Zlib> because as of this
writing it appears to be much faster than
L<IO::Uncompress::Gunzip|IO::Uncompress::Gunzip>.
-=item I<serialize_methods>
+=item serialize_methods
serialize_methods => [ \&Storable::freeze, \&Storable::thaw ],
(default: [ \&Storable::nfreeze, \&Storable::thaw ])
@@ -353,8 +434,26 @@ if deserialization fails (say, wrong data format) it should throw an
exception (call I<die>). The exception will be caught by the module
and L</get> will then pretend that the key hasn't been found.
-=back
+=item deconversion
+
+ deconversion => 1
+ (default: deconversion => 1)
+
+Controls whether I<de>-compression and I<de>-serialization are performed on
+apparently serialized or compressed values.
+
+Default is enabled.
+
+=item dereference_scalar_ref
+
+ dereference_scalar_ref => 1
+ (default: dereference_scalar_ref => 0)
+
+Controls whether a SCALAR reference is 'serialized' as normal via storable,
+or whether it should be dereferenced, and its underlying string used as a plain
+scalar value.
+=back
=head3 get(key)
@@ -542,6 +641,59 @@ Multi version of L</arithmetic>
=head3 decr_multi( [key, amount], ... )
+=head2 RUNTIME SETTINGS
+
+The following methods can be called without an argument, in which case it acts
+as a getter, and returns the boolean status of the relevant setting.
+
+If called with a single argument, that argument is a boolean value and the
+method acts as a mutator. The old value for the setting is returned.
+
+
+=head3 enable_compress(...), compression_settings(...)
+
+=head3 serialization_settings(...)
+
+These methods, when called with no arguments, will return the boolean status about
+whether compression or serialization is enabled.
+
+If passed an argument, the argument is converted to a boolean value, and the
+previous setting is returned.
+
+C<enable_compress> is an alias to C<compression_settings>, for API familiarity
+with older clients.
+
+=head3 conversion_settings(...)
+
+This is a catch-all setting for all modes of conversion; i.e. serialization
+B<and> compression. Disabling conversion will disable compression and serialization.
+
+Enabling conversion will restore the previous serialization and compression
+settings.
+
+=head3 deconversion_settings(...)
+
+This controls and accesses the deconversion setting. Deconversion is any
+I<decompression> or I<deserialization> when retrieving a remote value. This can
+be particularly handy if you wish to perform more heuristics on the type of
+the value, rather than possibly have the deconversion settings fail.
+
+When deconversion is disabled, all conversion settings are disabled as well.
+
+=head3 compress_threshold(...)
+
+Gets or sets the compression threshold, i.e. the minimum value length before
+compression is applied.
+
+
+=head3 timeout(...)
+
+Get or set the timeout for enqueued operations. The timeout is the time the client
+waits for a response after sending the request to the server.
+
+Timeouts cannot be disabled. See documentation on constructor options.
+
+
=head2 INFORMATIONAL METHODS
=head3 get_errors()
View
2  lib/Couchbase/Client/Async.pm
@@ -1,7 +1,7 @@
package Couchbase::Client::Async;
use strict;
use warnings;
-our $VERSION = '0.01_1';
+our $VERSION = '0.10_0';
require XSLoader;
XSLoader::load('Couchbase::Client', $VERSION);
use Couchbase::Client;
View
4 lib/Couchbase/Client/Errors_const.pm
@@ -1,7 +1,7 @@
package Couchbase::Client::Errors;
# This module was generated automatically by ExtUtils::H2PM from error_constants.pl
-push @EXPORT, 'COUCHBASE_SUCCESS', 'COUCHBASE_AUTH_CONTINUE', 'COUCHBASE_AUTH_ERROR', 'COUCHBASE_DELTA_BADVAL', 'COUCHBASE_E2BIG', 'COUCHBASE_EBUSY', 'COUCHBASE_EINTERNAL', 'COUCHBASE_EINVAL', 'COUCHBASE_ENOMEM', 'COUCHBASE_ERANGE', 'COUCHBASE_ERROR', 'COUCHBASE_ETMPFAIL', 'COUCHBASE_KEY_EEXISTS', 'COUCHBASE_KEY_ENOENT', 'COUCHBASE_LIBEVENT_ERROR', 'COUCHBASE_NETWORK_ERROR', 'COUCHBASE_NOT_MY_VBUCKET', 'COUCHBASE_NOT_STORED', 'COUCHBASE_NOT_SUPPORTED', 'COUCHBASE_UNKNOWN_COMMAND', 'COUCHBASE_UNKNOWN_HOST', 'COUCHBASE_PROTOCOL_ERROR', 'COUCHBASE_BUCKET_ENOENT';
+push @EXPORT, 'COUCHBASE_SUCCESS', 'COUCHBASE_AUTH_CONTINUE', 'COUCHBASE_AUTH_ERROR', 'COUCHBASE_DELTA_BADVAL', 'COUCHBASE_E2BIG', 'COUCHBASE_EBUSY', 'COUCHBASE_EINTERNAL', 'COUCHBASE_EINVAL', 'COUCHBASE_ENOMEM', 'COUCHBASE_ERANGE', 'COUCHBASE_ERROR', 'COUCHBASE_ETMPFAIL', 'COUCHBASE_KEY_EEXISTS', 'COUCHBASE_KEY_ENOENT', 'COUCHBASE_LIBEVENT_ERROR', 'COUCHBASE_NETWORK_ERROR', 'COUCHBASE_NOT_MY_VBUCKET', 'COUCHBASE_NOT_STORED', 'COUCHBASE_NOT_SUPPORTED', 'COUCHBASE_UNKNOWN_COMMAND', 'COUCHBASE_UNKNOWN_HOST', 'COUCHBASE_PROTOCOL_ERROR', 'COUCHBASE_ETIMEDOUT', 'COUCHBASE_CONNECT_ERROR', 'COUCHBASE_BUCKET_ENOENT';
use constant COUCHBASE_SUCCESS => 0;
use constant COUCHBASE_AUTH_CONTINUE => 1;
use constant COUCHBASE_AUTH_ERROR => 2;
@@ -24,6 +24,8 @@ use constant COUCHBASE_NOT_SUPPORTED => 18;
use constant COUCHBASE_UNKNOWN_COMMAND => 19;
use constant COUCHBASE_UNKNOWN_HOST => 20;
use constant COUCHBASE_PROTOCOL_ERROR => 21;
+use constant COUCHBASE_ETIMEDOUT => 22;
+use constant COUCHBASE_CONNECT_ERROR => 23;
use constant COUCHBASE_BUCKET_ENOENT => 24;
1;
View
5 lib/Couchbase/Client/IDXConst_const.pm
@@ -1,7 +1,7 @@
package Couchbase::Client::IDXConst;
# This module was generated automatically by ExtUtils::H2PM from idx_constants.pl
-push @EXPORT, 'CTORIDX_SERVERS', 'CTORIDX_USERNAME', 'CTORIDX_PASSWORD', 'CTORIDX_BUCKET', 'CTORIDX_STOREFLAGS', 'CTORIDX_MYFLAGS', 'CTORIDX_COMP_THRESHOLD', 'CTORIDX_COMP_METHODS', 'CTORIDX_SERIALIZE_METHODS', 'CTORIDX_TIMEOUT', 'CTORIDX_NO_CONNECT', 'RETIDX_VALUE', 'RETIDX_ERRSTR', 'RETIDX_CAS', 'RETIDX_ERRNUM', 'fUSE_COMPAT_FLAGS', 'fUSE_COMPRESSION', 'fUSE_STORABLE', 'fUSE_CONVERT_UTF8', 'fNO_CONNECT', 'fNO_DECONVERT', 'CTORIDX_CBEVMOD', 'CTORIDX_CBERR', 'CTORIDX_CBTIMERMOD', 'CTORIDX_CBWAITDONE', 'CTORIDX_BLESS_EVENT', 'COUCHBASE_READ_EVENT', 'COUCHBASE_WRITE_EVENT', 'REQIDX_KEY', 'REQIDX_VALUE', 'REQIDX_EXP', 'REQIDX_CAS', 'REQIDX_ARITH_DELTA', 'REQIDX_ARITH_INITIAL', 'REQIDX_STAT_ARGS', 'PLCBA_CMD_SET', 'PLCBA_CMD_GET', 'PLCBA_CMD_ADD', 'PLCBA_CMD_REPLACE', 'PLCBA_CMD_APPEND', 'PLCBA_CMD_PREPEND', 'PLCBA_CMD_REMOVE', 'PLCBA_CMD_TOUCH', 'PLCBA_CMD_ARITHMETIC', 'PLCBA_CMD_STATS', 'PLCBA_CMD_FLUSH', 'REQTYPE_SINGLE', 'REQTYPE_MULTI', 'CBTYPE_COMPLETION', 'CBTYPE_INCREMENTAL', 'EVIDX_FD', 'EVIDX_DUPFH', 'EVIDX_WATCHFLAGS', 'EVIDX_STATEFLAGS', 'EVIDX_OPAQUE', 'EVIDX_PLDATA', 'EVACTION_WATCH', 'EVACTION_UNWATCH', 'EVACTION_SUSPEND', 'EVACTION_RESUME', 'EVSTATE_INITIALIZED', 'EVSTATE_ACTIVE', 'EVSTATE_SUSPENDED';
+push @EXPORT, 'CTORIDX_SERVERS', 'CTORIDX_USERNAME', 'CTORIDX_PASSWORD', 'CTORIDX_BUCKET', 'CTORIDX_STOREFLAGS', 'CTORIDX_MYFLAGS', 'CTORIDX_COMP_THRESHOLD', 'CTORIDX_COMP_METHODS', 'CTORIDX_SERIALIZE_METHODS', 'CTORIDX_TIMEOUT', 'CTORIDX_NO_CONNECT', 'RETIDX_VALUE', 'RETIDX_ERRSTR', 'RETIDX_CAS', 'RETIDX_ERRNUM', 'fUSE_COMPAT_FLAGS', 'fUSE_COMPRESSION', 'fUSE_STORABLE', 'fUSE_CONVERT_UTF8', 'fNO_CONNECT', 'fDECONVERT', 'fDEREF_RVPV', 'CTORIDX_CBEVMOD', 'CTORIDX_CBERR', 'CTORIDX_CBTIMERMOD', 'CTORIDX_CBWAITDONE', 'CTORIDX_BLESS_EVENT', 'COUCHBASE_READ_EVENT', 'COUCHBASE_WRITE_EVENT', 'REQIDX_KEY', 'REQIDX_VALUE', 'REQIDX_EXP', 'REQIDX_CAS', 'REQIDX_ARITH_DELTA', 'REQIDX_ARITH_INITIAL', 'REQIDX_STAT_ARGS', 'PLCBA_CMD_SET', 'PLCBA_CMD_GET', 'PLCBA_CMD_ADD', 'PLCBA_CMD_REPLACE', 'PLCBA_CMD_APPEND', 'PLCBA_CMD_PREPEND', 'PLCBA_CMD_REMOVE', 'PLCBA_CMD_TOUCH', 'PLCBA_CMD_ARITHMETIC', 'PLCBA_CMD_STATS', 'PLCBA_CMD_FLUSH', 'REQTYPE_SINGLE', 'REQTYPE_MULTI', 'CBTYPE_COMPLETION', 'CBTYPE_INCREMENTAL', 'EVIDX_FD', 'EVIDX_DUPFH', 'EVIDX_WATCHFLAGS', 'EVIDX_STATEFLAGS', 'EVIDX_OPAQUE', 'EVIDX_PLDATA', 'EVACTION_WATCH', 'EVACTION_UNWATCH', 'EVACTION_SUSPEND', 'EVACTION_RESUME', 'EVSTATE_INITIALIZED', 'EVSTATE_ACTIVE', 'EVSTATE_SUSPENDED';
use constant CTORIDX_SERVERS => 0;
use constant CTORIDX_USERNAME => 1;
use constant CTORIDX_PASSWORD => 2;
@@ -22,7 +22,8 @@ use constant fUSE_COMPRESSION => 4;
use constant fUSE_STORABLE => 8;
use constant fUSE_CONVERT_UTF8 => 16;
use constant fNO_CONNECT => 32;
-use constant fNO_DECONVERT => 64;
+use constant fDECONVERT => 64;
+use constant fDEREF_RVPV => 512;
use constant CTORIDX_CBEVMOD => 10;
use constant CTORIDX_CBERR => 12;
use constant CTORIDX_CBTIMERMOD => 11;
View
31 lib/Couchbase/MockServer.pm
@@ -135,7 +135,7 @@ sub new {
unlink("$dir/$SYMLINK");
symlink($fqpath, "$dir/$SYMLINK");
-
+ #
#Initialize buckets to their defaults
if(!$o->buckets) {
$o->buckets([{
@@ -160,6 +160,35 @@ sub GetInstance {
return $INSTANCE;
}
+sub suspend_process {
+ my $self = shift;
+ my $pid = $self->pid;
+ return unless defined $pid;
+ kill SIGSTOP, $pid;
+}
+sub resume_process {
+ my $self = shift;
+ my $pid = $self->pid;
+ return unless defined $pid;
+ kill SIGCONT, $pid;
+}
+
+sub failover_node {
+ my ($self,$nodeidx,$bucket_name) = @_;
+ $bucket_name ||= "default";
+ my $cmd = "failover,$nodeidx,$bucket_name\n";
+ log_warn($cmd);
+ $self->harakiri_socket->send($cmd, 0) or die "Couldn't send";
+}
+
+sub respawn_node {
+ my ($self,$nodeidx,$bucket_name) = @_;
+ $bucket_name ||= "default";
+ my $cmd = "respawn,$nodeidx,$bucket_name\n";
+ log_warn($cmd);
+ $self->harakiri_socket->send($cmd, 0) or die "Couldn't send";
+}
+
sub DESTROY {
my $self = shift;
kill SIGTERM, $self->pid;
View
10 lib/Couchbase/Test/Async.pm
@@ -22,12 +22,14 @@ $poe_kernel->run();
my $ReadyReceived = 0;
my $Return = undef;
+my $Errnum;
sub setup_async :Test(startup) {
my $self = shift;
$self->mock_init();
Couchbase::Test::Async::Loop->spawn($loop_session,
on_ready => \&loop_ready,
+ on_error => sub { $Errnum = $_[0]; diag "Grrr!"; },
%{$self->common_options}
);
}
@@ -52,6 +54,7 @@ sub cb_result_single {
sub reset_vars :Test(setup) {
$ReadyReceived = 0;
$Return = undef;
+ $Errnum = -1;
}
sub post_to_loop {
@@ -65,8 +68,15 @@ sub post_to_loop {
}
sub T10_connect :Test(no_plan) {
+ my $self = shift;
$poe_kernel->run_one_timeslice() while ($ReadyReceived == 0);
+
ok($ReadyReceived, "Eventually connected..");
+ ok($Errnum <= 0, "Got no errors ($Errnum)");
+ if($Errnum > 0) {
+ die("Got errors. Cannot continue");
+ $self->FAIL_ALL("Async tests cannot continue without hanging");
+ }
}
sub T11_set :Test(no_plan) {
View
9 lib/Couchbase/Test/Async/Loop.pm
@@ -28,6 +28,7 @@ sub unhandled :Event(_default) {
sub got_error :Event {
log_errf("Got errnum=%d, errstr=%s",
$_[ARG0], $_[ARG1]);
+ $_[HEAP]->on_error(@_[ARG0,ARG1]);
}
@@ -154,14 +155,15 @@ sub dispatch_timeout :Event {
use Class::XSAccessor {
constructor => 'new',
- accessors => [qw(object alias on_ready)]
+ accessors => [qw(object alias on_ready on_error)]
};
sub spawn {
my ($cls,$session_name,%options) = @_;
my $cb_ready = delete $options{on_ready}
or die ("Must have on_ready callback");
-
+ my $user_error_callback = delete $options{on_error};
+
my $async = Couchbase::Client::Async->new({
%options,
cb_error =>
@@ -175,7 +177,8 @@ sub spawn {
sub { $poe_kernel->call($session_name, "update_timer", @_) }
});
- my $o = __PACKAGE__->new(alias => $session_name, object => $async);
+ my $o = __PACKAGE__->new(alias => $session_name, object => $async,
+ on_error => $user_error_callback);
POE::Session->create(
heap => $o,
inline_states =>
View
73 lib/Couchbase/Test/Common.pm
@@ -5,10 +5,22 @@ use base qw(Test::Class);
use Test::More;
use Couchbase::MockServer;
use Data::Dumper;
+use Class::XSAccessor {
+ accessors => [qw(mock res_buckets)]
+};
+
+my $have_confua = eval {
+ require Couchbase::Config::UA; 1;
+};
+
+my $have_vbucket = eval {
+ require Couchbase::VBucket; 1;
+};
our $Mock;
our $RealServer = $ENV{PLCB_TEST_REAL_SERVER};
our $MemdPort = $ENV{PLCB_TEST_MEMD_PORT};
+
sub mock_init
{
my $self = shift;
@@ -18,24 +30,67 @@ sub mock_init
$self->{mock} = $Mock;
}
-sub mock { $_[0]->{mock} }
+sub fetch_config {
+ my $self = shift;
+ if(!$have_confua) {
+ return;
+ }
+ my $confua = Couchbase::Config::UA->new(
+ $self->common_options->{server},
+ username => $self->common_options->{username},
+ password => $self->common_options->{password}
+ );
+ my $defpool = $confua->list_pools();
+ $confua->pool_info($defpool);
+ my $buckets = $confua->list_buckets($defpool);
+ $self->confua($confua);
+ $self->res_buckets($buckets);
+}
+
+use constant {
+ BUCKET_MEMCACHED => 1,
+ BUCKET_COUCHBASE => 2,
+ BUCKET_DEFAULT => 3
+};
sub common_options {
- my $self = shift;
+ my ($self,$bucket_type) = @_;
if($RealServer) {
return { %$RealServer };
}
-
+ my $mock = $self->mock;
my $opthash = {};
- my $defbucket = $self->mock->buckets->[0];
- if($defbucket->{password}) {
+ if(!$bucket_type) {
+ $bucket_type = BUCKET_DEFAULT;
+ } elsif ($bucket_type =~ /mem/) {
+ $bucket_type = BUCKET_MEMCACHED;
+ } elsif ($bucket_type =~ /couch/) {
+ $bucket_type = BUCKET_COUCHBASE;
+ } else {
+ warn("No such bucket type $bucket_type");
+ $bucket_type = BUCKET_DEFAULT;
+ }
+
+ my $bucket = $self->mock->buckets->[0] or die "No buckets!";
+ if($bucket_type == BUCKET_MEMCACHED) {
+ $bucket = (grep $_->{type} eq 'memcache',
+ @{$mock->buckets})[0];
+ } elsif ($bucket == BUCKET_COUCHBASE) {
+ $bucket = (grep { (!$_->{type}) || $_->{type} eq 'couchbase' }
+ @{$mock->buckets})[0];
+ }
+ if(!$bucket) {
+ die("Can't find common options for bucket (@_)");
+ }
+
+ if($bucket->{password}) {
$opthash->{username} = "some_user";
- $opthash->{password} = $defbucket->{password};
+ $opthash->{password} = $bucket->{password};
}
$opthash->{server} = "127.0.0.1:" . $self->mock->port;
- $opthash->{bucket} = $defbucket->{name};
+ $opthash->{bucket} = $bucket->{name};
return $opthash;
}
@@ -68,13 +123,15 @@ sub Initialize {
$RealServer = {};
foreach my $pair (@kvpairs) {
my ($k,$v) = split(/=/, $pair);
- $RealServer->{$k} = $v if $k =~ /server|bucket|username|password|memd_port/;
+ $RealServer->{$k} = $v if $k =~
+ /server|bucket|username|password|memd_port/;
}
$RealServer->{server} ||= "localhost:8091";
$RealServer->{bucket} ||= "default";
$MemdPort ||= delete $RealServer->{memd_port};
$Mock = 1;
} else {
+
$Mock = Couchbase::MockServer->new(%opts);
return $Mock;
}
View
100 lib/Couchbase/Test/Interop.pm
@@ -5,45 +5,40 @@ use base qw(Couchbase::Test::Common);
use Test::More;
use Couchbase::Client::Errors;
use Data::Dumper;
+Log::Fu::set_log_level('Couchbase::Config', 'info');
use Class::XSAccessor {
- accessors => [qw(cbo memds memd confua vbconf)]
+ accessors => [qw(cbo memd memds vbconf confua)]
};
my $MEMD_CLASS;
my $have_memcached =
eval {
- require Cache::Memcached::Fast;
- $MEMD_CLASS = "Cache::Memcached::Fast";
+ require Cache::Memcached::libmemcached;
+ $MEMD_CLASS = "Cache::Memcached::libmemcached";
} ||
eval {
require Cache::Memcached;
$MEMD_CLASS = "Cache::Memcached";
} ||
eval {
- require Cache::Memcached::libmemcached;
- $MEMD_CLASS = "Cache::Memcaced::libmemcached";
+ require Cache::Memcached::Fast;
+ $MEMD_CLASS = "Cache::Memcached::Fast";
};
+use Couchbase::Config::UA;
-sub setup_client :Test(startup) {
+sub _setup_client :Test(startup) {
my $self = shift;
if(!$have_memcached) {
$self->SKIP_ALL("Need Cache::Memcached::libmemcached");
}
- if(!$Couchbase::Test::Common::RealServer) {
- $self->SKIP_ALL("Need connection to real cluster");
- }
-
- if(!$Couchbase::Test::Common::MemdPort) {
- $self->SKIP_ALL("Need dedicated memcached proxy port");
- }
-
$self->mock_init();
- my $server = $self->common_options->{server};
+ my $server = $self->common_options->{server};
my $username = $self->common_options->{username};
my $password = $self->common_options->{password};
+ my $bucket_name = $self->common_options->{bucket};
my $cbo = Couchbase::Client->new({
%{$self->common_options}
@@ -51,42 +46,65 @@ sub setup_client :Test(startup) {
$self->cbo($cbo);
- my $memd = $MEMD_CLASS->new($self->memd_options);
+ my $confua = Couchbase::Config::UA->new(
+ $server, username => $username, password => $password);
+
+ my $pool = $confua->list_pools();
+ $confua->pool_info($pool);
+ my $buckets = $confua->list_buckets($pool);
+
+ my $bucket = (grep {
+ $_->name eq $bucket_name &&
+ $_->port_proxy || $_->type eq 'memcached'
+ } @$buckets)[0];
+
+ if(!$bucket) {
+ my $msg =
+ "Couldn't find appropriate bucket. Bucket must have an auth-less proxy ".
+ "port, and/or be of memcached type";
+ die $msg;
+ }
+ #print Dumper($bucket);
+
+ my $node = $bucket->nodes->[0];
+ my $memd_host = sprintf("%s:%d",
+ $node->base_addr,
+ $bucket->port_proxy ||
+ $node->port_proxy ||
+ $node->port_direct);
+
+
+ note "Have $memd_host";
+ my $memd = $MEMD_CLASS->new({servers => [ $memd_host] });
$self->memd($memd);
-}
-
-sub memd_for_key {
- my ($self,$key) = @_;
- return $self->memd;
+ if($memd->can('set_binary_protocol')) {
+ $memd->set_binary_protocol(1);
+ }
}
sub T30_interop_init :Test(no_plan)
{
my $self = shift;
- my $key = "Foo";
- my $value = "foo_value";
-
- my $memd = $self->memd;
-
- ok($memd->set($key, $value), "Set value OK");
- is($memd->get($key), $value, "Got back our value");
-
- my $ret = $self->cbo->get($key);
- ok($ret->is_ok, "Found value for memcached key");
- is($ret->value, $value, "Got back same value");
-
- $key = "bar";
- $value = "bar_value";
-
- ok($self->cbo->set($key,$value)->is_ok, "set via cbc");
- is($memd->get($key), $value, "get via memd");
+ my $memd = $self->memd();
+ foreach my $key qw(foo bar baz) {
+ my $value = scalar reverse($key);
+ ok($memd->set($key, $value), "Set value OK");
+ is($memd->get($key), $value, "Got back our value");
+
+ my $ret = $self->cbo->get($key);
+ ok($ret->is_ok, "Found value for memcached key");
+ is($ret->value, $value, "Got back same value");
+
+ ok($self->cbo->set($key,$value)->is_ok, "set via cbc");
+ is($memd->get($key), $value, "get via memd");
+ }
}
sub T31_interop_serialization :Test(no_plan) {
my $self = shift;
my $key = "Serialized";
my $value = [ qw(foo bar baz), { "this is" => "a hash" } ];
- my $memd = $self->memd_for_key($key);
+ my $memd = $self->memd();
ok($memd->set($key, $value), "Set serialized structure");
my $ret;
@@ -97,7 +115,9 @@ sub T31_interop_serialization :Test(no_plan) {
}
sub T32_interop_compression :Test(no_plan) {
-
+ my $self = shift;
+ my $key = "Compressed";
+ my $value = "foobarbaz" x 1000;
}
1;
View
114 lib/Couchbase/Test/Netfail.pm
@@ -0,0 +1,114 @@
+package Couchbase::Test::Netfail;
+use strict;
+use warnings;
+use Test::More;
+use base qw(Couchbase::Test::Common);
+use Couchbase::Client::Errors;
+use Couchbase::MockServer;
+use Time::HiRes qw(sleep);
+use Data::Dumper;
+
+use Class::XSAccessor {
+ accessors => [qw(cbo vbconf)]
+};
+
+my $have_vbucket = eval {
+ require Couchbase::Config::UA;
+ require Couchbase::VBucket;
+ 1;
+};
+
+sub startup_tests :Test(startup)
+{
+ my $self = shift;
+ $self->mock_init();
+ if($Couchbase::Test::Common::RealServer) {
+ $self->SKIP_ALL("Can't perform network tests on real server");
+ }
+ if($have_vbucket) {
+ my $confua = Couchbase::Config::UA->new(
+ $self->common_options->{server},
+ username => $self->common_options->{username},
+ password => $self->common_options->{password});
+ my $pool = $confua->list_pools();
+ $confua->pool_info($pool);
+ my $buckets = $confua->list_buckets($pool);
+ my $bucket = (grep {$_->name eq $self->common_options->{bucket}}
+ @$buckets)[0];
+ $self->vbconf($bucket->vbconf);
+ }
+
+}
+
+sub setup_test :Test(setup) {
+ my $self = shift;
+ my $options = $self->common_options("couchbase");
+ my $cbo = Couchbase::Client->new({
+ %{$self->common_options("couchbase")},
+ no_init_connect => 1
+ });
+ $self->cbo($cbo);
+ alarm(30); #things can hang, so don't wait more than a minute for each
+ #function
+ #$SIG{ALRM} = sub { diag "Alarm triggered"; die("grrr..."); }
+}
+
+sub teardown_test :Test(teardown) {
+ alarm(0);
+ #$SIG{ALRM} = 'DEFAULT';
+}
+
+sub T40_tmpfail_basic :Test(no_plan) {
+ my $self = shift;
+
+ my $cbo = $self->cbo;
+ my $mock = $self->mock;
+ my $wv;
+
+ note "Suspending mock server";
+ $mock->suspend_process();
+ $cbo->timeout(2);
+ ok(!$cbo->connect(), "Connect failed");
+ my $errors = $cbo->get_errors;
+ ok(scalar @$errors, "Have connection error");
+ is($errors->[0]->[0], COUCHBASE_CONNECT_ERROR, "CONNECT_ERROR");
+
+ note "Resuming mock server";
+ $mock->resume_process();
+ $wv = $cbo->connect();
+ $cbo->timeout(5);
+
+ ok($wv, "Connected ok");
+ ok($cbo->set("Foo", "foo_value")->is_ok, "set ok");
+}
+
+sub T41_degraded :Test(no_plan) {
+ return; #nothing to do here unfortunately
+ my $self = shift;
+ my $cbo = $self->cbo;
+ my $mock = $self->mock;
+
+ if(!$have_vbucket) {
+ $self->builder->skip("Need Couchbase::VBucket");
+ return;
+ }
+
+ my $key = "Foo";
+ my $value = "foofoo";
+
+ ok($cbo->connect(), "Connected ok (sanity check)");
+ ok($cbo->set($key,$value)->is_ok, "Set (initial, sanity check)");
+
+
+ my ($server,$idx) = $self->vbconf->map($key);
+ $mock->failover_node($idx, $self->common_options->{bucket});
+
+ my $ret = $cbo->set($key, $value);
+ is($ret->errnum, COUCHBASE_ETMPFAIL, "got expected error");
+
+ $mock->respawn_node($idx, $self->common_options->{bucket});
+ $ret = $cbo->set($key, $value);
+ ok($ret->is_ok, "Respawned node, set is OK");
+}
+
+1;
View
225 lib/Couchbase/Test/Settings.pm
@@ -10,19 +10,63 @@ use Class::XSAccessor {
accessors => [qw(cbo)]
};
+my $have_zlib = eval {
+ require Compress::Zlib;
+};
+
+my $SERIALIZATION_CALLED = 0;
+my $DESERIALIZATION_CALLED = 0;
+my $COMPRESSION_CALLED = 0;
+my $DECOMPRESSION_CALLED = 0;
+
+my $COMPRESS_METHOD;
+my $DECOMPRESS_METHOD;
+if($have_zlib) {
+ $COMPRESS_METHOD = sub {
+ $COMPRESSION_CALLED = 1;
+ ${$_[1]} = Compress::Zlib::memGzip(${$_[0]});
+ };
+ $DECOMPRESS_METHOD = sub {
+ $DECOMPRESSION_CALLED = 1;
+ ${$_[1]} = Compress::Zlib::memGunzip(${$_[0]});
+ };
+} else {
+ $COMPRESS_METHOD = sub {
+ $COMPRESSION_CALLED = 1;
+ ${$_[1]} = scalar reverse ${$_[0]};
+ };
+ $DECOMPRESS_METHOD = sub {
+ $DECOMPRESSION_CALLED = 1;
+ ${$_[1]} = scalar reverse ${$_[0]};
+ };
+}
+
sub setup_client :Test(startup)
{
my $self = shift;
- $self->mock_init();
-
+ $self->mock_init();
+}
+
+sub reset_vars {
+ $COMPRESSION_CALLED = 0;
+ $DECOMPRESSION_CALLED = 0;
+ $SERIALIZATION_CALLED = 0;
+ $DESERIALIZATION_CALLED = 0;
+}
+#We make a new client for each test
+sub _pretest :Test(setup) {
+ my $self = shift;
+ reset_vars();
my %options = (
%{$self->common_options},
- compress_threshold => 100
+ compress_threshold => 100,
+ compress_methods => [$COMPRESS_METHOD, $DECOMPRESS_METHOD]
);
my $o = Couchbase::Client->new(\%options);
$self->cbo( $o );
+
}
sub T20_settings_connect :Test(no_plan)
@@ -49,25 +93,188 @@ sub T20_settings_connect :Test(no_plan)
$client = Couchbase::Client->new({
%{$self->common_options},
- bucket => 'nonexist'
+ bucket => 'nonexist',
});
$errors = $client->get_errors();
ok(scalar @$errors, "Have error for nonexistent bucket");
is($errors->[0]->[0], COUCHBASE_BUCKET_ENOENT,
"Got BUCKET_ENOENT for nonexistent bucket");
+
+ my $warnmsg;
+ {
+ local $SIG{__WARN__} = sub { $warnmsg = shift };
+ ok($self->cbo->connect, "connect on connected instance returns OK");
+ like($warnmsg, qr/already connected/i,
+ "warning on already connected instance");
+ }
}
+sub T21_default_settings :Test(no_plan)
+{
+ my $self = shift;
+ my $cbo = Couchbase::Client->new({
+ no_init_connect => 1,
+ server => "localhost:0",
+ });
+
+ ok(!$cbo->dereference_scalar_ref_settings,
+ "SCALAR ref deref disabled by default");
+ ok($cbo->deconversion_settings, "deconversion enabled by default");
+ ok(!$cbo->enable_compress, "compression disabled by default");
+ ok($cbo->serialization_settings, "Serialization enabled by default");
+}
-sub T21_compress_settings :Test(no_plan)
+sub T22_compress_settings :Test(no_plan)
{
my $self = shift;
my $v;
- $v = $self->cbo->enable_compress();
- ok($v, "Compression enabled by default");
+ my $key = "compressed";
+ my $value = "foo" x 100;
+
+ my $cbo = $self->cbo;
+
+ my $ret = $cbo->set($key, $value);
+ ok($ret->is_ok, "No problem setting key");
+ is($COMPRESSION_CALLED, 1, "compression method called");
+
+ $ret = $cbo->get($key);
+ ok($ret->is_ok, "Got back our data");
+ is($ret->value, $value, "same value");
+ ok($DECOMPRESSION_CALLED, "Decompression method called");
+
+ $v = $cbo->enable_compress(0);
+ is($cbo->enable_compress, 0, "Compression disabled via setter");
+ reset_vars();
+
+ $ret = $cbo->get($key);
+ ok($ret->is_ok, "status OK");
+ ok($DECOMPRESSION_CALLED,
+ "decompression still called with compressiond disabled");
+ is($ret->value, $value, "Got same value");
+
+ reset_vars();
+ $ret = $cbo->set($key, $value);
+ ok($ret->is_ok, "storage operation ok");
+ is($COMPRESSION_CALLED, 0, "compression not called");
+
+ $ret = $cbo->get($key);
+ ok($ret->is_ok, "uncompressed retrieval ok");
+ is($DECOMPRESSION_CALLED, 0,
+ "decompression not called for non-compressed value");
+ is($ret->value, $value, "got same value");
+
+ reset_vars();
+ $cbo->enable_compress(1);
+ ok($cbo->enable_compress, "compression re-enabled");
+
+
+
+ $cbo->set($key, $value);
+ ok($COMPRESSION_CALLED,
+ "compression method called when compression re-enabled");
- $v = $self->cbo->enable_compress(0);
- is($self->cbo->enable_compress, 0, "Compression disabled via setter");
+ $cbo->enable_compress(0);
+ is($cbo->enable_compress, 0, "compression disabled");
+
+ ok($cbo->deconversion_settings, "deconversion still enabled");
+ $cbo->deconversion_settings(0);
+ is($cbo->deconversion_settings, 0, "deconversion now disabled");
+
+ reset_vars();
+ $ret = $cbo->get($key);
+ ok($ret->is_ok, "got compressed value ok");
+ is($DECOMPRESSION_CALLED, 0, "decompression not called");
+ ok($ret->value ne $value, "compressed data does not match original");
+
+ reset_vars();
+ $cbo->deconversion_settings(1);
+ $ret = $cbo->get($key);
+ is($ret->value, $value, "deconversion enabled, deompression enabled");
+}
+
+sub T23_serialize_settings :Test(no_plan)
+{
+ my $self = shift;
+ my $cbo = $self->cbo;
+
+ $cbo->serialization_settings(0);
+ $cbo->dereference_scalar_ref_settings(1);
+
+ #try to store a reference:
+
+ eval {
+ $cbo->set("serialized", [qw(foo bar baz)]);
+ };
+ ok($@, "got error for serializing data - ($@)");
+ is($SERIALIZATION_CALLED, 0, "serialization method not called on pre-check");
+
+ my $key = "compressed_key";
+ my $value = \"Hello world";
+
+ my $ret = $cbo->set($key, $value);
+ ok($ret->is_ok, "set value ok");
+ is($SERIALIZATION_CALLED, 0, "serialization not performed");
+
+ $ret = $cbo->get($key);
+ ok($ret->is_ok, "Got value ok");
+ is($ret->value, $$value, "dereference scalar ref");
}
+sub T24_timeout_settings :Test(no_plan)
+{
+ my $self = shift;
+ #here we can just get/set the timeout value, the real timeout tests happen
+ #in a different test module:
+ my $cbo = $self->cbo();
+ my $orig_timeo = $cbo->timeout;
+ is($orig_timeo, 2.5);
+
+
+ my $warnmsg;
+ {
+ local $SIG{__WARN__} = sub { $warnmsg = shift };
+ ok(!$cbo->timeout(-1), "Return nothing on bad argument");
+ };
+ like($warnmsg, qr/cannot disable timeouts/i, "cannot disable timeouts");
+ is($cbo->timeout, $orig_timeo, "still have the same timeout");
+
+ ok($cbo->timeout(0.1), "set timeout to value under 1");
+}
+
+sub T25_multi_server_list :Test(no_plan)
+{
+ my $self = shift;
+ my $server_list = ['localhost:0'];
+ my %options = %{$self->common_options};
+ my $bucket = $options{bucket};
+ my ($username,$password) = @options{qw(username password)};
+ push @$server_list, delete $options{server};
+ $options{servers} = $server_list;
+
+ my $errors;
+ my $cbo;
+ my $ret;
+
+ $cbo = Couchbase::Client->new({%options});
+ note "Connecting with bucket $bucket";
+ isa_ok($cbo, 'Couchbase::Client');
+ is(scalar @{$cbo->get_errors}, 1, "have single error");
+ is($cbo->get_errors->[0]->[0], COUCHBASE_NETWORK_ERROR,
+ "Got network error for nonexistent host");
+
+ $ret = $cbo->set("foo", "fooval");
+ ok($ret->is_ok, "connected and can set value (retry ok)");
+ if(!$ret->is_ok){
+ print Dumper($ret);
+ }
+ $cbo = Couchbase::Client->new({
+ %options,
+ servers => [$self->common_options->{server}, 'localhost:0'],
+ bucket => 'nonexistent'
+ });
+ is(scalar @{$cbo->get_errors}, 1, "Got one non-retriable error");
+ is($cbo->get_errors->[0]->[0], COUCHBASE_BUCKET_ENOENT,
+ "BUCKET_ENOENT as expected");
+}
1;
View
4 perl-couchbase.h
@@ -38,11 +38,13 @@ typedef enum {
PLCBf_USE_STORABLE = 0x8,
PLCBf_USE_CONVERT_UTF8 = 0x10,
PLCBf_NO_CONNECT = 0x20,
- PLCBf_NO_DECONVERT = 0x40,
+ PLCBf_DECONVERT = 0x40,
/*pseudo-flags*/
PLCBf_COMPRESS_THRESHOLD = 0x80,
PLCBf_RET_EXTENDED_FIELDS = 0x100,
+
+ PLCBf_DEREF_RVPV = 0x200,
} PLCB_flags_t;
#define PLCBf_DO_CONVERSION \
View
6 t/01-main.t
@@ -13,17 +13,19 @@ Couchbase::Test::Common->Initialize(
url => $config->{COUCHBASE_MOCK_JARURL},
dir => __DIR__ . "/tmp",
port => 8092,
- nodes => 2,
+ nodes => 5,
+ buckets => [{name => "default", type => "memcache"}],
);
use Couchbase::Test::ClientSync;
use Couchbase::Test::Async;
use Couchbase::Test::Settings;
use Couchbase::Test::Interop;
+use Couchbase::Test::Netfail;
Couchbase::Test::ClientSync->runtests();
Couchbase::Test::Async->runtests();
Couchbase::Test::Settings->runtests();
Couchbase::Test::Interop->runtests();
-
+Couchbase::Test::Netfail->runtests();
#Test::Class->runtests();
View
5 t/CouchAsync.pm
@@ -9,7 +9,7 @@ use Couchbase::Client::Errors;
use POE;
use POE::Kernel;
use Data::Dumper;
-use Log::Fu { level => "info" };
+use Log::Fu { level => "debug" };
use Devel::Peek;
use Array::Assign;
@@ -212,6 +212,7 @@ sub update_timer :Event {
if($usecs) {
$seconds = ($usecs / (1000*1000));
+ log_err($usecs);
}
if($action == EVACTION_WATCH) {
if(defined $timer_id) {
@@ -253,4 +254,4 @@ sub dispatch_timeout :Event {
POE::Sugar::Attributes->wire_new_session($SESSION);
-POE::Kernel->run();
+POE::Kernel->run();
Please sign in to comment.
Something went wrong with that request. Please try again.