riak-client 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +2 -2
- data/Gemfile.lock +12 -10
- data/bin/htmldiff +14 -0
- data/bin/ldiff +14 -0
- data/bin/rackup +14 -0
- data/bin/rake +14 -0
- data/bin/rspec +14 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +173 -0
- data/lib/riak.rb +3 -0
- data/lib/riak/robject.rb +43 -5
- data/lib/riak/test_server.rb +231 -0
- data/lib/riak/util/headers.rb +1 -1
- data/lib/riak/util/tcp_socket_extensions.rb +70 -0
- data/spec/integration/riak/cache_store_spec.rb +25 -10
- data/spec/integration/riak/test_server_spec.rb +174 -0
- data/spec/riak/object_spec.rb +98 -0
- data/spec/spec_helper.rb +14 -2
- data/spec/support/test_server.yml +2 -0
- metadata +15 -3
data/Gemfile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# A sample Gemfile
|
2
2
|
source :gemcutter
|
3
3
|
|
4
|
-
gem 'activesupport', '~>2.3.5' #
|
4
|
+
gem 'activesupport', '~>2.3.5' #'~>3.0.0'
|
5
5
|
gem 'i18n'
|
6
6
|
|
7
|
-
gem 'rspec', "~>2.0.0
|
7
|
+
gem 'rspec', "~>2.0.0"
|
8
8
|
gem 'fakeweb', ">=1.2"
|
9
9
|
gem 'rack', '>=1.0'
|
10
10
|
gem 'curb', '>=0.6'
|
data/Gemfile.lock
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (2.3.
|
4
|
+
activesupport (2.3.9)
|
5
5
|
curb (0.7.8)
|
6
6
|
diff-lcs (1.1.2)
|
7
7
|
fakeweb (1.3.0)
|
8
8
|
i18n (0.4.1)
|
9
9
|
rack (1.2.1)
|
10
10
|
rake (0.8.7)
|
11
|
-
rspec (2.0.0
|
12
|
-
rspec-core (= 2.0.0
|
13
|
-
rspec-expectations (= 2.0.0
|
14
|
-
rspec-mocks (= 2.0.0
|
15
|
-
rspec-core (2.0.0
|
16
|
-
rspec-expectations (2.0.0
|
11
|
+
rspec (2.0.0)
|
12
|
+
rspec-core (= 2.0.0)
|
13
|
+
rspec-expectations (= 2.0.0)
|
14
|
+
rspec-mocks (= 2.0.0)
|
15
|
+
rspec-core (2.0.0)
|
16
|
+
rspec-expectations (2.0.0)
|
17
17
|
diff-lcs (>= 1.1.2)
|
18
|
-
rspec-mocks (2.0.0
|
19
|
-
|
18
|
+
rspec-mocks (2.0.0)
|
19
|
+
rspec-core (= 2.0.0)
|
20
|
+
rspec-expectations (= 2.0.0)
|
21
|
+
yajl-ruby (0.7.8)
|
20
22
|
|
21
23
|
PLATFORMS
|
22
24
|
ruby
|
@@ -29,5 +31,5 @@ DEPENDENCIES
|
|
29
31
|
i18n
|
30
32
|
rack (>= 1.0)
|
31
33
|
rake
|
32
|
-
rspec (~> 2.0.0
|
34
|
+
rspec (~> 2.0.0)
|
33
35
|
yajl-ruby
|
data/bin/htmldiff
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'htmldiff' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", __FILE__)
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
|
14
|
+
load Gem.bin_path('diff-lcs', 'htmldiff')
|
data/bin/ldiff
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'ldiff' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", __FILE__)
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
|
14
|
+
load Gem.bin_path('diff-lcs', 'ldiff')
|
data/bin/rackup
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rackup' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", __FILE__)
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
|
14
|
+
load Gem.bin_path('rack', 'rackup')
|
data/bin/rake
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rake' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", __FILE__)
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
|
14
|
+
load Gem.bin_path('rake', 'rake')
|
data/bin/rspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", __FILE__)
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
|
14
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
Binary file
|
@@ -0,0 +1,173 @@
|
|
1
|
+
%% -------------------------------------------------------------------
|
2
|
+
%%
|
3
|
+
%% riak_kv_test_backend: storage engine based on ETS tables
|
4
|
+
%%
|
5
|
+
%% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved.
|
6
|
+
%%
|
7
|
+
%% This file is provided to you under the Apache License,
|
8
|
+
%% Version 2.0 (the "License"); you may not use this file
|
9
|
+
%% except in compliance with the License. You may obtain
|
10
|
+
%% a copy of the License at
|
11
|
+
%%
|
12
|
+
%% http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
%%
|
14
|
+
%% Unless required by applicable law or agreed to in writing,
|
15
|
+
%% software distributed under the License is distributed on an
|
16
|
+
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
17
|
+
%% KIND, either express or implied. See the License for the
|
18
|
+
%% specific language governing permissions and limitations
|
19
|
+
%% under the License.
|
20
|
+
%%
|
21
|
+
%% -------------------------------------------------------------------
|
22
|
+
|
23
|
+
% @doc riak_kv_test_backend is a Riak storage backend using ets that exposes a reset function for efficiently clearing stored data.
|
24
|
+
|
25
|
+
-module(riak_kv_test_backend).
|
26
|
+
-behavior(riak_kv_backend).
|
27
|
+
-behavior(gen_server).
|
28
|
+
-ifdef(TEST).
|
29
|
+
-include_lib("eunit/include/eunit.hrl").
|
30
|
+
-endif.
|
31
|
+
-export([start/2,stop/1,get/2,put/3,list/1,list_bucket/2,delete/2,
|
32
|
+
is_empty/1, drop/1, fold/3, callback/3, reset/0]).
|
33
|
+
|
34
|
+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
35
|
+
terminate/2, code_change/3]).
|
36
|
+
|
37
|
+
% @type state() = term().
|
38
|
+
-record(state, {t}).
|
39
|
+
|
40
|
+
% @spec start(Partition :: integer(), Config :: proplist()) ->
|
41
|
+
% {ok, state()} | {{error, Reason :: term()}, state()}
|
42
|
+
start(Partition, _Config) ->
|
43
|
+
gen_server:start_link(?MODULE, [Partition], []).
|
44
|
+
|
45
|
+
% @spec reset() -> ok
|
46
|
+
reset() ->
|
47
|
+
Pids = lists:filter(fun(Item) ->
|
48
|
+
lists:prefix("test_backend", atom_to_list(Item))
|
49
|
+
end, registered()),
|
50
|
+
[gen_server:call(Pid, reset) || Pid <- Pids],
|
51
|
+
ok.
|
52
|
+
|
53
|
+
%% @private
|
54
|
+
init([Partition]) ->
|
55
|
+
PName = list_to_atom("test_backend" ++ integer_to_list(Partition)),
|
56
|
+
register(PName, self()),
|
57
|
+
{ok, #state{t=ets:new(list_to_atom(integer_to_list(Partition)),[])}}.
|
58
|
+
|
59
|
+
%% @private
|
60
|
+
handle_cast(_, State) -> {noreply, State}.
|
61
|
+
|
62
|
+
%% @private
|
63
|
+
handle_call(stop,_From,State) -> {reply, srv_stop(State), State};
|
64
|
+
handle_call({get,BKey},_From,State) -> {reply, srv_get(State,BKey), State};
|
65
|
+
handle_call({put,BKey,Val},_From,State) ->
|
66
|
+
{reply, srv_put(State,BKey,Val),State};
|
67
|
+
handle_call({delete,BKey},_From,State) -> {reply, srv_delete(State,BKey),State};
|
68
|
+
handle_call(list,_From,State) -> {reply, srv_list(State), State};
|
69
|
+
handle_call({list_bucket,Bucket},_From,State) ->
|
70
|
+
{reply, srv_list_bucket(State, Bucket), State};
|
71
|
+
handle_call(is_empty, _From, State) ->
|
72
|
+
{reply, ets:info(State#state.t, size) =:= 0, State};
|
73
|
+
handle_call(drop, _From, State) ->
|
74
|
+
ets:delete(State#state.t),
|
75
|
+
{reply, ok, State};
|
76
|
+
handle_call({fold, Fun0, Acc}, _From, State) ->
|
77
|
+
Fun = fun({{B,K}, V}, AccIn) -> Fun0({B,K}, V, AccIn) end,
|
78
|
+
Reply = ets:foldl(Fun, Acc, State#state.t),
|
79
|
+
{reply, Reply, State};
|
80
|
+
handle_call(reset, _From, State) ->
|
81
|
+
ets:delete_all_objects(State#state.t),
|
82
|
+
{reply, ok, State}.
|
83
|
+
|
84
|
+
% @spec stop(state()) -> ok | {error, Reason :: term()}
|
85
|
+
stop(SrvRef) -> gen_server:call(SrvRef,stop).
|
86
|
+
srv_stop(State) ->
|
87
|
+
true = ets:delete(State#state.t),
|
88
|
+
ok.
|
89
|
+
|
90
|
+
% get(state(), riak_object:bkey()) ->
|
91
|
+
% {ok, Val :: binary()} | {error, Reason :: term()}
|
92
|
+
% key must be 160b
|
93
|
+
get(SrvRef, BKey) -> gen_server:call(SrvRef,{get,BKey}).
|
94
|
+
srv_get(State, BKey) ->
|
95
|
+
case ets:lookup(State#state.t,BKey) of
|
96
|
+
[] -> {error, notfound};
|
97
|
+
[{BKey,Val}] -> {ok, Val};
|
98
|
+
Err -> {error, Err}
|
99
|
+
end.
|
100
|
+
|
101
|
+
% put(state(), riak_object:bkey(), Val :: binary()) ->
|
102
|
+
% ok | {error, Reason :: term()}
|
103
|
+
% key must be 160b
|
104
|
+
put(SrvRef, BKey, Val) -> gen_server:call(SrvRef,{put,BKey,Val}).
|
105
|
+
srv_put(State,BKey,Val) ->
|
106
|
+
true = ets:insert(State#state.t, {BKey,Val}),
|
107
|
+
ok.
|
108
|
+
|
109
|
+
% delete(state(), riak_object:bkey()) ->
|
110
|
+
% ok | {error, Reason :: term()}
|
111
|
+
% key must be 160b
|
112
|
+
delete(SrvRef, BKey) -> gen_server:call(SrvRef,{delete,BKey}).
|
113
|
+
srv_delete(State, BKey) ->
|
114
|
+
true = ets:delete(State#state.t, BKey),
|
115
|
+
ok.
|
116
|
+
|
117
|
+
% list(state()) -> [riak_object:bkey()]
|
118
|
+
list(SrvRef) -> gen_server:call(SrvRef,list).
|
119
|
+
srv_list(State) ->
|
120
|
+
MList = ets:match(State#state.t,{'$1','_'}),
|
121
|
+
list(MList,[]).
|
122
|
+
list([],Acc) -> Acc;
|
123
|
+
list([[K]|Rest],Acc) -> list(Rest,[K|Acc]).
|
124
|
+
|
125
|
+
% list_bucket(term(), Bucket :: riak_object:bucket()) -> [Key :: binary()]
|
126
|
+
list_bucket(SrvRef, Bucket) ->
|
127
|
+
gen_server:call(SrvRef,{list_bucket, Bucket}).
|
128
|
+
srv_list_bucket(State, {filter, Bucket, Fun}) ->
|
129
|
+
MList = lists:filter(Fun, ets:match(State#state.t,{{Bucket,'$1'},'_'})),
|
130
|
+
list(MList,[]);
|
131
|
+
srv_list_bucket(State, Bucket) ->
|
132
|
+
case Bucket of
|
133
|
+
'_' -> MatchSpec = {{'$1','_'},'_'};
|
134
|
+
_ -> MatchSpec = {{Bucket,'$1'},'_'}
|
135
|
+
end,
|
136
|
+
MList = ets:match(State#state.t,MatchSpec),
|
137
|
+
list(MList,[]).
|
138
|
+
|
139
|
+
is_empty(SrvRef) -> gen_server:call(SrvRef, is_empty).
|
140
|
+
|
141
|
+
drop(SrvRef) -> gen_server:call(SrvRef, drop).
|
142
|
+
|
143
|
+
fold(SrvRef, Fun, Acc0) -> gen_server:call(SrvRef, {fold, Fun, Acc0}, infinity).
|
144
|
+
|
145
|
+
%% Ignore callbacks for other backends so multi backend works
|
146
|
+
callback(_State, _Ref, _Msg) ->
|
147
|
+
ok.
|
148
|
+
|
149
|
+
%% @private
|
150
|
+
handle_info(_Msg, State) -> {noreply, State}.
|
151
|
+
|
152
|
+
%% @private
|
153
|
+
terminate(_Reason, _State) -> ok.
|
154
|
+
|
155
|
+
%% @private
|
156
|
+
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
157
|
+
|
158
|
+
%%
|
159
|
+
%% Test
|
160
|
+
%%
|
161
|
+
-ifdef(TEST).
|
162
|
+
|
163
|
+
% @private
|
164
|
+
simple_test() ->
|
165
|
+
riak_kv_backend:standard_test(?MODULE, []).
|
166
|
+
|
167
|
+
-ifdef(EQC).
|
168
|
+
%% @private
|
169
|
+
eqc_test() ->
|
170
|
+
?assertEqual(true, backend_eqc:test(?MODULE, true)).
|
171
|
+
|
172
|
+
-endif. % EQC
|
173
|
+
-endif. % TEST
|
data/lib/riak.rb
CHANGED
@@ -44,6 +44,9 @@ module Riak
|
|
44
44
|
autoload :InvalidResponse, "riak/invalid_response"
|
45
45
|
autoload :MapReduceError, "riak/map_reduce_error"
|
46
46
|
|
47
|
+
# Test server
|
48
|
+
autoload :TestServer, "riak/test_server"
|
49
|
+
|
47
50
|
# Utility classes and mixins
|
48
51
|
module Util
|
49
52
|
autoload :Escape, "riak/util/escape"
|
data/lib/riak/robject.rb
CHANGED
@@ -35,9 +35,6 @@ module Riak
|
|
35
35
|
attr_accessor :vclock
|
36
36
|
alias_attribute :vector_clock, :vclock
|
37
37
|
|
38
|
-
# @return [Object] the data stored in Riak at this object's key. Varies in format by content-type, defaulting to String from the response body.
|
39
|
-
attr_accessor :data
|
40
|
-
|
41
38
|
# @return [Set<Link>] a Set of {Riak::Link} objects for relationships between this object and other resources
|
42
39
|
attr_accessor :links
|
43
40
|
|
@@ -50,6 +47,10 @@ module Riak
|
|
50
47
|
# @return [Hash] a hash of any X-Riak-Meta-* headers that were in the HTTP response, keyed on the trailing portion
|
51
48
|
attr_accessor :meta
|
52
49
|
|
50
|
+
# @return [Boolean] whether to attempt to prevent stale writes using conditional PUT semantics, If-None-Match: * or If-Match: {#etag}
|
51
|
+
# @see http://wiki.basho.com/display/RIAK/REST+API#RESTAPI-Storeaneworexistingobjectwithakey Riak Rest API Docs
|
52
|
+
attr_accessor :prevent_stale_writes
|
53
|
+
|
53
54
|
# Loads a list of RObjects that were emitted from a MapReduce
|
54
55
|
# query.
|
55
56
|
# @param [Client] client A Riak::Client with which the results will be associated
|
@@ -89,7 +90,7 @@ module Riak
|
|
89
90
|
end
|
90
91
|
@conflict = response[:code].try(:to_i) == 300 && content_type =~ /multipart\/mixed/
|
91
92
|
@siblings = nil
|
92
|
-
|
93
|
+
self.raw_data = response[:body] if response[:body].present?
|
93
94
|
self
|
94
95
|
end
|
95
96
|
|
@@ -115,12 +116,49 @@ module Riak
|
|
115
116
|
self
|
116
117
|
end
|
117
118
|
|
119
|
+
# @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
|
120
|
+
def data
|
121
|
+
if @raw_data && !@data
|
122
|
+
@data = deserialize(@raw_data)
|
123
|
+
@raw_data = nil
|
124
|
+
end
|
125
|
+
@data
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param [Object] unmarshaled form of the data to be stored in riak. Object will be serialized using {#serialize} if a known content_type is used. Setting this overrides values stored with {#raw_data=}
|
129
|
+
# @return [Object] the object stored
|
130
|
+
def data=(new_data)
|
131
|
+
@raw_data = nil
|
132
|
+
@data = new_data
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [String] raw data stored in riak for this object's key
|
136
|
+
def raw_data
|
137
|
+
if @data && !@raw_data
|
138
|
+
@raw_data = serialize(@data)
|
139
|
+
@data = nil
|
140
|
+
end
|
141
|
+
@raw_data
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [String, IO] the raw data to be stored in riak at this key, will not be marshaled or manipulated prior to storage. Overrides any data stored by {#data=}
|
145
|
+
# @return [String] the data stored
|
146
|
+
def raw_data=(new_raw_data)
|
147
|
+
@data = nil
|
148
|
+
@raw_data = new_raw_data
|
149
|
+
end
|
150
|
+
|
118
151
|
# HTTP header hash that will be sent along when storing the object
|
119
152
|
# @return [Hash] hash of HTTP Headers
|
120
153
|
def store_headers
|
121
154
|
{}.tap do |hash|
|
122
155
|
hash["Content-Type"] = @content_type
|
123
156
|
hash["X-Riak-Vclock"] = @vclock if @vclock
|
157
|
+
if @prevent_stale_writes && @etag.present?
|
158
|
+
hash["If-Match"] = @etag
|
159
|
+
elsif @prevent_stale_writes
|
160
|
+
hash["If-None-Match"] = "*"
|
161
|
+
end
|
124
162
|
unless @links.blank?
|
125
163
|
hash["Link"] = @links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
|
126
164
|
end
|
@@ -153,7 +191,7 @@ module Riak
|
|
153
191
|
raise ArgumentError, t("content_type_undefined") unless @content_type.present?
|
154
192
|
params = {:returnbody => true}.merge(options)
|
155
193
|
method, codes, path = @key.present? ? [:put, [200,204,300], "#{escape(@bucket.name)}/#{escape(@key)}"] : [:post, 201, escape(@bucket.name)]
|
156
|
-
response = @bucket.client.http.send(method, codes, @bucket.client.prefix, path, params,
|
194
|
+
response = @bucket.client.http.send(method, codes, @bucket.client.prefix, path, params, raw_data, store_headers)
|
157
195
|
load(response)
|
158
196
|
end
|
159
197
|
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
require 'tempfile'
|
16
|
+
require 'expect'
|
17
|
+
require 'open3'
|
18
|
+
require 'riak/util/tcp_socket_extensions'
|
19
|
+
|
20
|
+
module Riak
|
21
|
+
class TestServer
|
22
|
+
APP_CONFIG_DEFAULTS = {
|
23
|
+
:riak_core => {
|
24
|
+
:web_ip => "127.0.0.1",
|
25
|
+
:web_port => 9000,
|
26
|
+
:handoff_port => 9001,
|
27
|
+
:ring_creation_size => 64
|
28
|
+
},
|
29
|
+
:riak_kv => {
|
30
|
+
:storage_backend => :riak_kv_test_backend,
|
31
|
+
:pb_ip => "127.0.0.1",
|
32
|
+
:pb_port => 9002,
|
33
|
+
:js_vm_count => 8,
|
34
|
+
:js_max_vm_mem => 8,
|
35
|
+
:js_thread_stack => 16,
|
36
|
+
:riak_kv_stat => true
|
37
|
+
},
|
38
|
+
:luwak => {
|
39
|
+
:enabled => false
|
40
|
+
}
|
41
|
+
}
|
42
|
+
VM_ARGS_DEFAULTS = {
|
43
|
+
"-name" => "riaktest#{rand(1000000).to_s}@127.0.0.1",
|
44
|
+
"-setcookie" => "#{rand(1000000).to_s}_#{rand(1000000).to_s}",
|
45
|
+
"+K" => true,
|
46
|
+
"+A" => 64,
|
47
|
+
"-smp" => "enable",
|
48
|
+
"-env ERL_MAX_PORTS" => 4096,
|
49
|
+
"-env ERL_FULLSWEEP_AFTER" => 10,
|
50
|
+
"-pa" => File.expand_path("../../../erl_src", __FILE__)
|
51
|
+
}
|
52
|
+
DEFAULTS = {
|
53
|
+
:app_config => APP_CONFIG_DEFAULTS,
|
54
|
+
:vm_args => VM_ARGS_DEFAULTS,
|
55
|
+
:temp_dir => File.join(Dir.tmpdir,'riaktest'),
|
56
|
+
}
|
57
|
+
attr_accessor :temp_dir, :app_config, :vm_args, :cin, :cout, :cerr, :cpid
|
58
|
+
|
59
|
+
def initialize(options={})
|
60
|
+
options = deep_merge(DEFAULTS.dup, options)
|
61
|
+
@temp_dir = File.expand_path(options[:temp_dir])
|
62
|
+
@bin_dir = File.expand_path(options[:bin_dir])
|
63
|
+
options[:app_config][:riak_core][:ring_state_dir] ||= File.join(@temp_dir, "data", "ring")
|
64
|
+
@app_config = options[:app_config]
|
65
|
+
@vm_args = options[:vm_args]
|
66
|
+
# For synchronizing start/stop/recycle
|
67
|
+
@mutex = Mutex.new
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sets up the proper scripts, configuration and directories for
|
71
|
+
# the test server. Call at the top of your test suite (not in a
|
72
|
+
# setup method).
|
73
|
+
def prepare!
|
74
|
+
unless @prepared
|
75
|
+
create_temp_directories
|
76
|
+
@riak_script = File.join(@temp_bin, 'riak')
|
77
|
+
write_riak_script
|
78
|
+
write_vm_args
|
79
|
+
write_app_config
|
80
|
+
@prepared = true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Starts the test server if it is not already running, and waits
|
85
|
+
# for it to respond to pings.
|
86
|
+
def start
|
87
|
+
if @prepared && !@started
|
88
|
+
@mutex.synchronize do
|
89
|
+
@cin, @cout, @cerr, @cpid = Open3.popen3("#{@riak_script} console")
|
90
|
+
@cin.puts
|
91
|
+
wait_for_erlang_prompt
|
92
|
+
@started = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Stops the test server if it is running.
|
98
|
+
def stop
|
99
|
+
if @started
|
100
|
+
@mutex.synchronize do
|
101
|
+
begin
|
102
|
+
@cin.puts "init:stop()."
|
103
|
+
rescue Errno::EPIPE
|
104
|
+
ensure
|
105
|
+
register_stop
|
106
|
+
end
|
107
|
+
end
|
108
|
+
true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Whether the server has been started.
|
113
|
+
def started?
|
114
|
+
@started
|
115
|
+
end
|
116
|
+
|
117
|
+
# Causes the entire contents of the Riak in-memory backends to be
|
118
|
+
# dumped by performing a soft restart.
|
119
|
+
def recycle
|
120
|
+
if @started
|
121
|
+
@mutex.synchronize do
|
122
|
+
begin
|
123
|
+
if @app_config[:riak_kv][:storage_backend] == :riak_kv_test_backend
|
124
|
+
@cin.puts "riak_kv_test_backend:reset()."
|
125
|
+
wait_for_erlang_prompt
|
126
|
+
else
|
127
|
+
@cin.puts "init:restart()."
|
128
|
+
wait_for_erlang_prompt
|
129
|
+
wait_for_startup
|
130
|
+
end
|
131
|
+
rescue Errno::EPIPE
|
132
|
+
warn "Broken pipe when recycling, is Riak alive?"
|
133
|
+
register_stop
|
134
|
+
return false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
true
|
138
|
+
else
|
139
|
+
start
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Cleans up any files and directories generated by the test
|
144
|
+
# server.
|
145
|
+
def cleanup
|
146
|
+
stop if @started
|
147
|
+
FileUtils.rm_rf(@temp_dir)
|
148
|
+
@prepared = false
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
def create_temp_directories
|
153
|
+
%w{bin etc log data pipe}.each do |dir|
|
154
|
+
instance_variable_set("@temp_#{dir}", File.expand_path(File.join(@temp_dir, dir)))
|
155
|
+
FileUtils.mkdir_p(instance_variable_get("@temp_#{dir}"))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def write_riak_script
|
160
|
+
File.open(@riak_script, 'wb') do |f|
|
161
|
+
File.readlines(File.join(@bin_dir, 'riak')).each do |line|
|
162
|
+
line.sub!(/(RUNNER_SCRIPT_DIR=)(.*)/, '\1' + @temp_bin)
|
163
|
+
line.sub!(/(RUNNER_ETC_DIR=)(.*)/, '\1' + @temp_etc)
|
164
|
+
line.sub!(/(RUNNER_USER=)(.*)/, '\1')
|
165
|
+
line.sub!(/(RUNNER_LOG_DIR=)(.*)/, '\1' + @temp_log)
|
166
|
+
line.sub!(/(PIPE_DIR=)(.*)/, '\1' + @temp_pipe)
|
167
|
+
if line.strip == "RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}"
|
168
|
+
line = "RUNNER_BASE_DIR=#{File.expand_path("..",@bin_dir)}\n"
|
169
|
+
end
|
170
|
+
f.write line
|
171
|
+
end
|
172
|
+
end
|
173
|
+
FileUtils.chmod(0755,@riak_script)
|
174
|
+
end
|
175
|
+
|
176
|
+
def write_vm_args
|
177
|
+
File.open(File.join(@temp_etc, 'vm.args'), 'wb') do |f|
|
178
|
+
f.write @vm_args.map {|k,v| "#{k} #{v}" }.join("\n")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def write_app_config
|
183
|
+
File.open(File.join(@temp_etc, 'app.config'), 'wb') do |f|
|
184
|
+
f.write to_erlang_config(@app_config) + '.'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def deep_merge(source, target)
|
189
|
+
source.merge(target) do |key, old_val, new_val|
|
190
|
+
if Hash === old_val && Hash === new_val
|
191
|
+
deep_merge(old_val, new_val)
|
192
|
+
else
|
193
|
+
new_val
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def to_erlang_config(hash, depth = 1)
|
199
|
+
padding = ' ' * depth
|
200
|
+
parent_padding = ' ' * (depth-1)
|
201
|
+
values = hash.map do |k,v|
|
202
|
+
printable = case v
|
203
|
+
when Hash
|
204
|
+
to_erlang_config(v, depth+1)
|
205
|
+
when String
|
206
|
+
"\"#{v}\""
|
207
|
+
else
|
208
|
+
v.to_s
|
209
|
+
end
|
210
|
+
"{#{k}, #{printable}}"
|
211
|
+
end.join(",\n#{padding}")
|
212
|
+
"[\n#{padding}#{values}\n#{parent_padding}]"
|
213
|
+
end
|
214
|
+
|
215
|
+
def wait_for_startup
|
216
|
+
TCPSocket.wait_for_service_with_timeout(:host => @app_config[:riak_core][:web_ip],
|
217
|
+
:port => @app_config[:riak_core][:web_port], :timeout => 10)
|
218
|
+
end
|
219
|
+
|
220
|
+
def wait_for_erlang_prompt
|
221
|
+
@cout.expect(/\(#{Regexp.escape(vm_args["-name"])}\)\d+>/)
|
222
|
+
end
|
223
|
+
|
224
|
+
def register_stop
|
225
|
+
%w{@cin @cout @cerr}.each {|io| if instance_variable_get(io); instance_variable_get(io).close; instance_variable_set(io, nil) end }
|
226
|
+
_cpid = @cpid; @cpid = nil
|
227
|
+
at_exit { _cpid.join if _cpid.alive? }
|
228
|
+
@started = false
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
data/lib/riak/util/headers.rb
CHANGED
@@ -21,7 +21,7 @@ module Net::HTTPHeader
|
|
21
21
|
respond_to?(:enum_for) and (block_given? or return enum_for(__method__))
|
22
22
|
@header.each do |k,v|
|
23
23
|
base_length = "#{k}: \r\n".length
|
24
|
-
values = v.map {|i| i.split(", ") }.flatten
|
24
|
+
values = v.map {|i| i.to_s.split(", ") }.flatten
|
25
25
|
while !values.empty?
|
26
26
|
current_line = ""
|
27
27
|
while values.first && current_line.length + base_length + values.first.length + 2 < 8192
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'timeout'
|
15
|
+
require 'socket'
|
16
|
+
|
17
|
+
# Borrowed from Webrat and Selenium client, watches for TCP port
|
18
|
+
# liveness of the spawned server.
|
19
|
+
# @private
|
20
|
+
class TCPSocket
|
21
|
+
def self.wait_for_service(options)
|
22
|
+
verbose_wait until listening_service?(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.wait_for_service_termination(options)
|
26
|
+
verbose_wait while listening_service?(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.listening_service?(options)
|
30
|
+
Timeout::timeout(options[:timeout] || 20) do
|
31
|
+
begin
|
32
|
+
socket = TCPSocket.new(options[:host], options[:port])
|
33
|
+
socket.close unless socket.nil?
|
34
|
+
true
|
35
|
+
rescue Errno::ECONNREFUSED,
|
36
|
+
Errno::EBADF # Windows
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.verbose_wait
|
43
|
+
# Removed the puts call so as not to clutter up test output.
|
44
|
+
sleep 2
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.wait_for_service_with_timeout(options)
|
48
|
+
start_time = Time.now
|
49
|
+
|
50
|
+
until listening_service?(options)
|
51
|
+
verbose_wait
|
52
|
+
|
53
|
+
if options[:timeout] && (Time.now > start_time + options[:timeout])
|
54
|
+
raise SocketError.new("Socket did not open within #{options[:timeout]} seconds")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.wait_for_service_termination_with_timeout(options)
|
60
|
+
start_time = Time.now
|
61
|
+
|
62
|
+
while listening_service?(options)
|
63
|
+
verbose_wait
|
64
|
+
|
65
|
+
if options[:timeout] && (Time.now > start_time + options[:timeout])
|
66
|
+
raise SocketError.new("Socket did not terminate within #{options[:timeout]} seconds")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -14,15 +14,30 @@
|
|
14
14
|
require File.expand_path("../../spec_helper", File.dirname(__FILE__))
|
15
15
|
|
16
16
|
describe Riak::CacheStore do
|
17
|
+
before :all do
|
18
|
+
if $test_server
|
19
|
+
@web_port = 9000
|
20
|
+
$test_server.start
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
17
24
|
before do
|
18
|
-
@
|
25
|
+
@web_port ||= 8098
|
26
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :port => @web_port)
|
19
27
|
@cleanup = true
|
20
28
|
end
|
21
29
|
|
22
30
|
after do
|
23
|
-
@
|
24
|
-
|
25
|
-
|
31
|
+
if @cleanup
|
32
|
+
if $test_server
|
33
|
+
$test_server.recycle
|
34
|
+
Thread.current[:curl_easy_handle] = nil
|
35
|
+
else
|
36
|
+
@cache.bucket.keys(:force => true).each do |k|
|
37
|
+
@cache.bucket.delete(k, :rw => 1) unless k.blank?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
26
41
|
end
|
27
42
|
|
28
43
|
describe "Riak integration" do
|
@@ -45,7 +60,7 @@ describe Riak::CacheStore do
|
|
45
60
|
end
|
46
61
|
|
47
62
|
it "should choose the bucket according to the initializer option" do
|
48
|
-
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :bucket => "foobar")
|
63
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :bucket => "foobar", :port => @web_port)
|
49
64
|
@cache.bucket.name.should == "foobar"
|
50
65
|
end
|
51
66
|
|
@@ -54,7 +69,7 @@ describe Riak::CacheStore do
|
|
54
69
|
end
|
55
70
|
|
56
71
|
it "should set the N value to the specified value" do
|
57
|
-
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :n_value => 1)
|
72
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :n_value => 1, :port => @web_port)
|
58
73
|
@cache.bucket.n_value.should == 1
|
59
74
|
end
|
60
75
|
|
@@ -63,7 +78,7 @@ describe Riak::CacheStore do
|
|
63
78
|
end
|
64
79
|
|
65
80
|
it "should set the bucket R default to the specified value" do
|
66
|
-
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :r => "quorum")
|
81
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :r => "quorum", :port => @web_port)
|
67
82
|
@cache.bucket.r.should == "quorum"
|
68
83
|
end
|
69
84
|
|
@@ -72,7 +87,7 @@ describe Riak::CacheStore do
|
|
72
87
|
end
|
73
88
|
|
74
89
|
it "should set the bucket W default to the specified value" do
|
75
|
-
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :w => "all")
|
90
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :w => "all", :port => @web_port)
|
76
91
|
@cache.bucket.w.should == "all"
|
77
92
|
end
|
78
93
|
|
@@ -81,7 +96,7 @@ describe Riak::CacheStore do
|
|
81
96
|
end
|
82
97
|
|
83
98
|
it "should set the bucket DW default to the specified value" do
|
84
|
-
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :dw => "quorum")
|
99
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :dw => "quorum", :port => @web_port)
|
85
100
|
@cache.bucket.dw.should == "quorum"
|
86
101
|
end
|
87
102
|
|
@@ -90,7 +105,7 @@ describe Riak::CacheStore do
|
|
90
105
|
end
|
91
106
|
|
92
107
|
it "should set the bucket RW default to the specified value" do
|
93
|
-
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :rw => "all")
|
108
|
+
@cache = ActiveSupport::Cache.lookup_store(:riak_store, :rw => "all", :port => @web_port)
|
94
109
|
@cache.bucket.rw.should == "all"
|
95
110
|
end
|
96
111
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require File.expand_path("../../spec_helper", File.dirname(__FILE__))
|
15
|
+
|
16
|
+
if $test_server
|
17
|
+
describe Riak::TestServer do
|
18
|
+
before do
|
19
|
+
@server = $test_server
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
@server.stop
|
24
|
+
@server.cleanup
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "isolation from and modification of the existing install" do
|
28
|
+
before do
|
29
|
+
@server.prepare!
|
30
|
+
@riak_bin = "#{@server.temp_dir}/bin/riak"
|
31
|
+
@vm_args = "#{@server.temp_dir}/etc/vm.args"
|
32
|
+
@app_config = "#{@server.temp_dir}/etc/app.config"
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "for app.config" do
|
36
|
+
it "should create the app.config file in the temporary directory" do
|
37
|
+
File.should be_exist(File.expand_path(@app_config))
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be a correct Erlang config" do
|
41
|
+
config = File.read(@app_config)
|
42
|
+
config[-2..-1].should == '].'
|
43
|
+
config[0..0].should == '['
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should set the backend to use the cache backend" do
|
47
|
+
File.readlines(@app_config).should be_any do |line|
|
48
|
+
line =~ /\{storage_backend\s*,\s*(.*)\}/ && $1 == "riak_kv_cache_backend"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should set the default ports to 9000-9002" do
|
53
|
+
config = File.readlines(@app_config)
|
54
|
+
config.should be_any do |line|
|
55
|
+
line =~ /\{web_port\s*,\s*(.*)\}/ && $1 == "9000"
|
56
|
+
end
|
57
|
+
config.should be_any do |line|
|
58
|
+
line =~ /\{handoff_port\s*,\s*(.*)\}/ && $1 == "9001"
|
59
|
+
end
|
60
|
+
config.should be_any do |line|
|
61
|
+
line =~ /\{pb_port\s*,\s*(.*)\}/ && $1 == "9002"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should set the ring directory to point to the temporary directory" do
|
66
|
+
config = File.readlines(@app_config)
|
67
|
+
config.should be_any do |line|
|
68
|
+
line =~ /\{ring_state_dir\s*,\s*(.*)\}/ && $1 == File.join(@server.temp_dir, "data", "ring")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "for vm.args" do
|
74
|
+
it "should create the vm.args file in the temporary directory" do
|
75
|
+
File.should be_exist(File.expand_path(@vm_args))
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should set a quasi-random node name" do
|
79
|
+
File.readlines(@vm_args).should be_any do |line|
|
80
|
+
line =~ /^-name (.*)/ && $1 =~ /riaktest\d+@/
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should set a quasi-random cookie" do
|
85
|
+
File.readlines(@vm_args).should be_any do |line|
|
86
|
+
line =~ /^-setcookie (.*)/ && $1 != "riak"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "for the riak script" do
|
92
|
+
it "should create the script in the temporary directory" do
|
93
|
+
File.should be_exist(File.expand_path(@riak_bin))
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should modify the RUNNER_SCRIPT_DIR to point to the temporary directory" do
|
97
|
+
File.readlines(@riak_bin).should be_any do |line|
|
98
|
+
line =~ /RUNNER_SCRIPT_DIR=(.*)/ && $1 == File.expand_path("#{@server.temp_dir}/bin")
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should modify the RUNNER_ETC_DIR to point to the temporary directory" do
|
104
|
+
File.readlines(@riak_bin).should be_any do |line|
|
105
|
+
line =~ /RUNNER_ETC_DIR=(.*)/ && $1 == File.expand_path("#{@server.temp_dir}/etc")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should modify the RUNNER_USER to point to the current user" do
|
110
|
+
File.readlines(@riak_bin).should be_any do |line|
|
111
|
+
line =~ /RUNNER_USER=(.*)/ && $1 == (ENV['USER'] || `whoami`)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should modify the RUNNER_LOG_DIR to point to the temporary directory" do
|
116
|
+
File.readlines(@riak_bin).should be_any do |line|
|
117
|
+
line =~ /RUNNER_LOG_DIR=(.*)/ && $1 == File.expand_path("#{@server.temp_dir}/log")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should modify the RUNNER_BASE_DIR so that it is not relative" do
|
122
|
+
File.readlines(@riak_bin).should be_any do |line|
|
123
|
+
line =~ /RUNNER_BASE_DIR=(.*)/ && $1.strip != "${RUNNER_SCRIPT_DIR%/*}" && File.directory?($1)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should modify the PIPE_DIR to point to the temporary directory" do
|
128
|
+
File.readlines(@riak_bin).should be_any do |line|
|
129
|
+
line =~ /PIPE_DIR=(.*)/ && $1 == File.expand_path("#{@server.temp_dir}/pipe") && File.directory?($1)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should cleanup the existing config" do
|
136
|
+
@server.prepare!
|
137
|
+
@server.cleanup
|
138
|
+
File.should_not be_directory(@server.temp_dir)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should start Riak in the background" do
|
142
|
+
@server.prepare!
|
143
|
+
@server.start.should be_true
|
144
|
+
@server.should be_started
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should stop a started test server" do
|
148
|
+
@server.prepare!
|
149
|
+
@server.start.should be_true
|
150
|
+
@server.stop
|
151
|
+
@server.should_not be_started
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should recycle the server contents" do
|
155
|
+
begin
|
156
|
+
@server.prepare!
|
157
|
+
@server.start.should be_true
|
158
|
+
|
159
|
+
client = Riak::Client.new(:port => 9000)
|
160
|
+
obj = client['test_bucket'].new("test_item")
|
161
|
+
obj.data = {"data" => "testing"}
|
162
|
+
obj.store rescue nil
|
163
|
+
|
164
|
+
@server.recycle
|
165
|
+
@server.should be_started
|
166
|
+
lambda do
|
167
|
+
client['test_bucket']['test_item']
|
168
|
+
end.should raise_error(Riak::FailedRequest)
|
169
|
+
ensure
|
170
|
+
@server.stop
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
data/spec/riak/object_spec.rb
CHANGED
@@ -118,6 +118,64 @@ describe Riak::RObject do
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
+
describe "data access methods" do
|
122
|
+
before :each do
|
123
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
124
|
+
@object.content_type = "application/json"
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "for raw data" do
|
128
|
+
describe "when unserialized data was already provided" do
|
129
|
+
before do
|
130
|
+
@object.data = { :some => :data }
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should reset unserialized forms when stored" do
|
134
|
+
@object.raw_data = value = '{ "raw": "json" }'
|
135
|
+
|
136
|
+
@object.raw_data.should == value
|
137
|
+
@object.data.should == { "raw" => "json" }
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should lazily serialize when read" do
|
141
|
+
@object.raw_data.should == '{"some":"data"}'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should not unnecessarily marshal/demarshal" do
|
146
|
+
@object.should_not_receive(:serialize)
|
147
|
+
@object.should_not_receive(:deserialize)
|
148
|
+
@object.raw_data = value = "{not even valid json!}}"
|
149
|
+
@object.raw_data.should == value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "for unserialized data" do
|
154
|
+
describe "when raw data was already provided" do
|
155
|
+
before do
|
156
|
+
@object.raw_data = '{"some":"data"}'
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should reset previously stored raw data" do
|
160
|
+
@object.data = value = { "new" => "data" }
|
161
|
+
@object.raw_data.should == '{"new":"data"}'
|
162
|
+
@object.data.should == value
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should lazily deserialize when read" do
|
166
|
+
@object.data.should == { "some" => "data" }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should not unnecessarily marshal/demarshal" do
|
171
|
+
@object.should_not_receive(:serialize)
|
172
|
+
@object.should_not_receive(:deserialize)
|
173
|
+
@object.data = value = { "some" => "data" }
|
174
|
+
@object.data.should == value
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
121
179
|
describe "loading data from the response" do
|
122
180
|
before :each do
|
123
181
|
@object = Riak::RObject.new(@bucket, "bar")
|
@@ -130,9 +188,16 @@ describe Riak::RObject do
|
|
130
188
|
|
131
189
|
it "should load the body data" do
|
132
190
|
@object.load({:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
|
191
|
+
@object.raw_data.should be_present
|
133
192
|
@object.data.should be_present
|
134
193
|
end
|
135
194
|
|
195
|
+
it "should handle raw data properly" do
|
196
|
+
@object.should_not_receive(:deserialize) # optimize for the raw_data case, don't penalize people for using raw_data
|
197
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => body = '{"foo":"bar"}'})
|
198
|
+
@object.raw_data.should == body
|
199
|
+
end
|
200
|
+
|
136
201
|
it "should deserialize the body data" do
|
137
202
|
@object.should_receive(:deserialize).with("{}").and_return({})
|
138
203
|
@object.load({:headers => {"content-type" => ["application/json"]}, :body => "{}"})
|
@@ -335,6 +400,23 @@ describe Riak::RObject do
|
|
335
400
|
@object.store_headers.should_not have_key("X-Riak-Vclock")
|
336
401
|
end
|
337
402
|
|
403
|
+
describe "when conditional PUTs are requested" do
|
404
|
+
before :each do
|
405
|
+
@object.prevent_stale_writes = true
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should include an If-None-Match: * header" do
|
409
|
+
@object.store_headers.should have_key("If-None-Match")
|
410
|
+
@object.store_headers["If-None-Match"].should == "*"
|
411
|
+
end
|
412
|
+
|
413
|
+
it "should include an If-Match header with the etag when an etag is present" do
|
414
|
+
@object.etag = "foobar"
|
415
|
+
@object.store_headers.should have_key("If-Match")
|
416
|
+
@object.store_headers["If-Match"].should == @object.etag
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
338
420
|
describe "when links are defined" do
|
339
421
|
before :each do
|
340
422
|
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
@@ -447,6 +529,22 @@ describe Riak::RObject do
|
|
447
529
|
@object.key = "bar"
|
448
530
|
end
|
449
531
|
|
532
|
+
describe "when the content is of a known serializable type" do
|
533
|
+
before :each do
|
534
|
+
@object.content_type = "application/json"
|
535
|
+
@headers = @object.store_headers
|
536
|
+
end
|
537
|
+
|
538
|
+
it "should not serialize content if #raw_data is used" do
|
539
|
+
storing = @object.raw_data = "{this is probably invalid json}}"
|
540
|
+
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, storing, @headers).and_return({:headers => {"x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
541
|
+
@object.should_not_receive(:serialize)
|
542
|
+
@object.should_not_receive(:deserialize)
|
543
|
+
@object.store
|
544
|
+
@object.raw_data.should == storing
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
450
548
|
it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
|
451
549
|
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
452
550
|
@object.store
|
data/spec/spec_helper.rb
CHANGED
@@ -16,14 +16,26 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
16
16
|
|
17
17
|
require 'rubygems' # Use the gems path only for the spec suite
|
18
18
|
require 'riak'
|
19
|
-
require 'rspec
|
19
|
+
require 'rspec'
|
20
20
|
require 'fakeweb'
|
21
21
|
|
22
|
+
begin
|
23
|
+
require 'yaml'
|
24
|
+
config = YAML.load_file("spec/support/test_server.yml")
|
25
|
+
$test_server = Riak::TestServer.new(config.symbolize_keys)
|
26
|
+
$test_server.prepare!
|
27
|
+
$test_server.start
|
28
|
+
at_exit { $test_server.cleanup }
|
29
|
+
rescue => e
|
30
|
+
warn "Can't run Riak::TestServer specs. Specify the location of your Riak installation in spec/support/test_server.yml. See Riak::TestServer docs for more info."
|
31
|
+
warn e.inspect
|
32
|
+
end
|
33
|
+
|
22
34
|
Dir[File.join(File.dirname(__FILE__), "support", "*.rb")].each {|f| require f }
|
23
35
|
|
24
36
|
Rspec.configure do |config|
|
25
37
|
config.mock_with :rspec
|
26
|
-
|
38
|
+
|
27
39
|
config.before(:each) do
|
28
40
|
FakeWeb.clean_registry
|
29
41
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 8
|
8
|
-
-
|
9
|
-
version: 0.8.
|
8
|
+
- 1
|
9
|
+
version: 0.8.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Sean Cribbs
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-10-11 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -115,6 +115,13 @@ extensions: []
|
|
115
115
|
extra_rdoc_files: []
|
116
116
|
|
117
117
|
files:
|
118
|
+
- bin/htmldiff
|
119
|
+
- bin/ldiff
|
120
|
+
- bin/rackup
|
121
|
+
- bin/rake
|
122
|
+
- bin/rspec
|
123
|
+
- erl_src/riak_kv_test_backend.beam
|
124
|
+
- erl_src/riak_kv_test_backend.erl
|
118
125
|
- Gemfile
|
119
126
|
- Gemfile.lock
|
120
127
|
- lib/active_support/cache/riak_store.rb
|
@@ -132,10 +139,12 @@ files:
|
|
132
139
|
- lib/riak/map_reduce.rb
|
133
140
|
- lib/riak/map_reduce_error.rb
|
134
141
|
- lib/riak/robject.rb
|
142
|
+
- lib/riak/test_server.rb
|
135
143
|
- lib/riak/util/escape.rb
|
136
144
|
- lib/riak/util/fiber1.8.rb
|
137
145
|
- lib/riak/util/headers.rb
|
138
146
|
- lib/riak/util/multipart.rb
|
147
|
+
- lib/riak/util/tcp_socket_extensions.rb
|
139
148
|
- lib/riak/util/translation.rb
|
140
149
|
- lib/riak/walk_spec.rb
|
141
150
|
- lib/riak.rb
|
@@ -144,6 +153,7 @@ files:
|
|
144
153
|
- spec/fixtures/multipart-blank.txt
|
145
154
|
- spec/fixtures/multipart-with-body.txt
|
146
155
|
- spec/integration/riak/cache_store_spec.rb
|
156
|
+
- spec/integration/riak/test_server_spec.rb
|
147
157
|
- spec/riak/bucket_spec.rb
|
148
158
|
- spec/riak/client_spec.rb
|
149
159
|
- spec/riak/curb_backend_spec.rb
|
@@ -160,6 +170,7 @@ files:
|
|
160
170
|
- spec/support/http_backend_implementation_examples.rb
|
161
171
|
- spec/support/mock_server.rb
|
162
172
|
- spec/support/mocks.rb
|
173
|
+
- spec/support/test_server.yml
|
163
174
|
has_rdoc: true
|
164
175
|
homepage: http://seancribbs.github.com/ripple
|
165
176
|
licenses: []
|
@@ -194,6 +205,7 @@ specification_version: 3
|
|
194
205
|
summary: riak-client is a rich client for Riak, the distributed database by Basho.
|
195
206
|
test_files:
|
196
207
|
- spec/integration/riak/cache_store_spec.rb
|
208
|
+
- spec/integration/riak/test_server_spec.rb
|
197
209
|
- spec/riak/bucket_spec.rb
|
198
210
|
- spec/riak/client_spec.rb
|
199
211
|
- spec/riak/curb_backend_spec.rb
|