riak-client 0.9.0.beta → 0.9.0.beta2

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.
Files changed (50) hide show
  1. data/Gemfile +10 -7
  2. data/Rakefile +21 -3
  3. data/erl_src/riak_kv_test_backend.beam +0 -0
  4. data/erl_src/riak_kv_test_backend.erl +29 -13
  5. data/lib/riak/bucket.rb +1 -1
  6. data/lib/riak/cache_store.rb +1 -1
  7. data/lib/riak/client.rb +119 -8
  8. data/lib/riak/client/beefcake/messages.rb +162 -0
  9. data/lib/riak/client/beefcake/object_methods.rb +92 -0
  10. data/lib/riak/client/beefcake_protobuffs_backend.rb +186 -0
  11. data/lib/riak/client/curb_backend.rb +10 -16
  12. data/lib/riak/client/excon_backend.rb +14 -18
  13. data/lib/riak/client/http_backend.rb +13 -13
  14. data/lib/riak/client/http_backend/object_methods.rb +1 -1
  15. data/lib/riak/client/http_backend/transport_methods.rb +6 -2
  16. data/lib/riak/client/net_http_backend.rb +33 -20
  17. data/lib/riak/client/protobuffs_backend.rb +103 -0
  18. data/lib/riak/client/pump.rb +44 -0
  19. data/lib/riak/failed_request.rb +58 -3
  20. data/lib/riak/locale/en.yml +11 -3
  21. data/lib/riak/map_reduce.rb +15 -6
  22. data/lib/riak/map_reduce/filter_builder.rb +4 -4
  23. data/lib/riak/test_server.rb +5 -1
  24. data/lib/riak/util/multipart.rb +30 -16
  25. data/lib/riak/util/multipart/stream_parser.rb +74 -0
  26. data/riak-client.gemspec +14 -12
  27. data/spec/fixtures/server.cert.crt +15 -0
  28. data/spec/fixtures/server.cert.key +15 -0
  29. data/spec/fixtures/test.pem +1 -0
  30. data/spec/integration/riak/http_backends_spec.rb +45 -0
  31. data/spec/integration/riak/protobuffs_backends_spec.rb +45 -0
  32. data/spec/integration/riak/test_server_spec.rb +2 -2
  33. data/spec/riak/bucket_spec.rb +4 -4
  34. data/spec/riak/client_spec.rb +209 -3
  35. data/spec/riak/excon_backend_spec.rb +8 -7
  36. data/spec/riak/http_backend/configuration_spec.rb +64 -0
  37. data/spec/riak/http_backend/object_methods_spec.rb +1 -1
  38. data/spec/riak/http_backend/transport_methods_spec.rb +129 -0
  39. data/spec/riak/http_backend_spec.rb +13 -1
  40. data/spec/riak/map_reduce/filter_builder_spec.rb +45 -0
  41. data/spec/riak/map_reduce/phase_spec.rb +149 -0
  42. data/spec/riak/map_reduce_spec.rb +5 -5
  43. data/spec/riak/net_http_backend_spec.rb +1 -0
  44. data/spec/riak/{object_spec.rb → robject_spec.rb} +1 -1
  45. data/spec/riak/stream_parser_spec.rb +66 -0
  46. data/spec/support/drb_mock_server.rb +2 -2
  47. data/spec/support/http_backend_implementation_examples.rb +27 -0
  48. data/spec/support/mock_server.rb +22 -1
  49. data/spec/support/unified_backend_examples.rb +255 -0
  50. metadata +43 -54
@@ -23,14 +23,14 @@ else
23
23
 
24
24
  describe Riak::Client::ExconBackend do
25
25
  def setup_http_mock(method, uri, options={})
26
- method = method.to_s.upcase
27
- uri = URI.parse(uri)
28
- path = uri.path || "/"
29
- query = uri.query || ""
30
- status = options[:status] ? Array(options[:status]).first.to_i : 200
31
- body = options[:body] || []
26
+ method = method.to_s.upcase
27
+ uri = URI.parse(uri)
28
+ path = uri.path || "/"
29
+ query = uri.query || ""
30
+ body = options[:body] || []
32
31
  headers = options[:headers] || {}
33
- headers['Content-Type'] ||= "text/plain"
32
+ headers['Content-Type'] ||= "text/plain"
33
+ status = options[:status] ? Array(options[:status]).first.to_i : 200
34
34
  @_mock_set = [status, headers, method, path, query, body]
35
35
  $mock_server.expect(*@_mock_set)
36
36
  end
@@ -73,4 +73,5 @@ else
73
73
  end.should_not raise_error
74
74
  end
75
75
  end
76
+
76
77
  end
@@ -0,0 +1,64 @@
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
+ describe Riak::Client::HTTPBackend::Configuration do
17
+ before do
18
+ @client = Riak::Client.new
19
+ @backend = Riak::Client::HTTPBackend.new(@client)
20
+ end
21
+
22
+ it "should memoize the server config" do
23
+ @backend.should_receive(:get).with(200, "/", {}, {}).once.and_return(:headers => {'link' => ['</riak>; rel="riak_kv_wm_link_walker",</mapred>; rel="riak_kv_wm_mapred",</ping>; rel="riak_kv_wm_ping",</riak>; rel="riak_kv_wm_raw",</stats>; rel="riak_kv_wm_stats"']})
24
+ @backend.send(:riak_kv_wm_link_walker).should == "/riak"
25
+ @backend.send(:riak_kv_wm_raw).should == "/riak"
26
+ end
27
+
28
+ {
29
+ :riak_kv_wm_raw => :prefix,
30
+ :riak_kv_wm_link_walker => :prefix,
31
+ :riak_kv_wm_mapred => :mapred
32
+ }.each do |resource, alternate|
33
+ it "should detect the #{resource} resource from the configuration URL" do
34
+ @backend.should_receive(:get).with(200, "/", {}, {}).and_return(:headers => {'link' => [%Q{</path>; rel="#{resource}"}]})
35
+ @backend.send(resource).should == "/path"
36
+ end
37
+ it "should fallback to client.#{alternate} if the #{resource} resource is not found" do
38
+ @backend.should_receive(:get).with(200, "/", {}, {}).and_return(:headers => {'link' => ['</>; rel="top"']})
39
+ @backend.send(resource).should == @client.send(alternate)
40
+ end
41
+ it "should fallback to client.#{alternate} if request fails" do
42
+ @backend.should_receive(:get).with(200, "/", {}, {}).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 404, {}, ""))
43
+ @backend.send(resource).should == @client.send(alternate)
44
+ end
45
+ end
46
+
47
+ {
48
+ :riak_kv_wm_ping => "/ping",
49
+ :riak_kv_wm_stats => "/stats"
50
+ }.each do |resource, default|
51
+ it "should detect the #{resource} resource from the configuration URL" do
52
+ @backend.should_receive(:get).with(200, "/", {}, {}).and_return(:headers => {'link' => [%Q{</path>; rel="#{resource}"}]})
53
+ @backend.send(resource).should == "/path"
54
+ end
55
+ it "should fallback to #{default.inspect} if the #{resource} resource is not found" do
56
+ @backend.should_receive(:get).with(200, "/", {}, {}).and_return(:headers => {'link' => ['</>; rel="top"']})
57
+ @backend.send(resource).should == default
58
+ end
59
+ it "should fallback to #{default.inspect} if request fails" do
60
+ @backend.should_receive(:get).with(200, "/", {}, {}).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 404, {}, ""))
61
+ @backend.send(resource).should == default
62
+ end
63
+ end
64
+ end
@@ -84,7 +84,7 @@ describe Riak::Client::HTTPBackend::ObjectMethods do
84
84
  end
85
85
 
86
86
  it "should parse and escape the location header into the key when present" do
87
- @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/%5Bbaz%5D"]}})
87
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/%5Bbaz%5D?vtag=1234"]}})
88
88
  @object.key.should == "[baz]"
89
89
  end
90
90
 
@@ -0,0 +1,129 @@
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
+ describe Riak::Client::HTTPBackend::TransportMethods do
17
+ before :each do
18
+ @client = Riak::Client.new
19
+ @backend = Riak::Client::HTTPBackend.new(@client)
20
+ @backend.instance_variable_set(:@server_config, {})
21
+ end
22
+
23
+ it "should generate default headers for requests based on the client settings" do
24
+ @client.client_id = "testing"
25
+ @backend.default_headers.should == {"X-Riak-ClientId" => "testing", "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5"}
26
+ end
27
+
28
+ it "should generate a root URI based on the client settings" do
29
+ @backend.root_uri.should be_kind_of(URI)
30
+ @backend.root_uri.to_s.should == "http://127.0.0.1:8098"
31
+ end
32
+
33
+ it "should compute a URI from a relative resource path" do
34
+ @backend.path("baz").should be_kind_of(URI)
35
+ @backend.path("foo").to_s.should == "http://127.0.0.1:8098/foo"
36
+ @backend.path("foo", "bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
37
+ @backend.path("/foo/bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
38
+ end
39
+
40
+ it "should compute a URI from a relative resource path with a hash of query parameters" do
41
+ @backend.path("baz", :r => 2).to_s.should == "http://127.0.0.1:8098/baz?r=2"
42
+ end
43
+
44
+ it "should raise an error if a resource path is too short" do
45
+ lambda { @backend.verify_path!(["/riak/"]) }.should raise_error(ArgumentError)
46
+ lambda { @backend.verify_path!(["/riak/", "foo"]) }.should_not raise_error
47
+ lambda { @backend.verify_path!(["/mapred"]) }.should_not raise_error
48
+ end
49
+
50
+ describe "verify_path_and_body!" do
51
+ it "should separate the path and body from given arguments" do
52
+ uri, data = @backend.verify_path_and_body!(["/riak/", "foo", "This is the body."])
53
+ uri.should == ["/riak/", "foo"]
54
+ data.should == "This is the body."
55
+ end
56
+
57
+ it "should raise an error if the body is not a string or IO" do
58
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo", nil]) }.should raise_error(ArgumentError)
59
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo", File.open("spec/fixtures/cat.jpg")]) }.should_not raise_error(ArgumentError)
60
+ end
61
+
62
+ it "should raise an error if a body is not given" do
63
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo"])}.should raise_error(ArgumentError)
64
+ end
65
+
66
+ it "should raise an error if a path is not given" do
67
+ lambda { @backend.verify_path_and_body!(["/riak/"])}.should raise_error(ArgumentError)
68
+ end
69
+ end
70
+
71
+ describe "detecting valid response codes" do
72
+ it "should accept strings or integers for either argument" do
73
+ @backend.should be_valid_response("300", "300")
74
+ @backend.should be_valid_response(300, "300")
75
+ @backend.should be_valid_response("300", 300)
76
+ end
77
+
78
+ it "should accept an array of strings or integers for the expected code" do
79
+ @backend.should be_valid_response([200,304], "200")
80
+ @backend.should be_valid_response(["200",304], "200")
81
+ @backend.should be_valid_response([200,"304"], "200")
82
+ @backend.should be_valid_response(["200","304"], "200")
83
+ @backend.should be_valid_response([200,304], 200)
84
+ end
85
+
86
+ it "should be false when none of the response codes match" do
87
+ @backend.should_not be_valid_response(200, 404)
88
+ @backend.should_not be_valid_response(["200","304"], 404)
89
+ @backend.should_not be_valid_response([200,304], 404)
90
+ end
91
+ end
92
+
93
+ describe "detecting whether a body should be returned" do
94
+ it "should be false when the method is :head" do
95
+ @backend.should_not be_return_body(:head, 200, false)
96
+ end
97
+
98
+ it "should be false when the response code is 204, 205, or 304" do
99
+ @backend.should_not be_return_body(:get, 204, false)
100
+ @backend.should_not be_return_body(:get, 205, false)
101
+ @backend.should_not be_return_body(:get, 304, false)
102
+ end
103
+
104
+ it "should be false when a streaming block was passed" do
105
+ @backend.should_not be_return_body(:get, 200, true)
106
+ end
107
+
108
+ it "should be true when the method is not head, a code other than 204, 205, or 304 was given, and there was no streaming block" do
109
+ [:get, :put, :post, :delete].each do |method|
110
+ [100,101,200,201,202,203,206,300,301,302,303,305,307,400,401,
111
+ 402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,
112
+ 500,501,502,503,504,505].each do |code|
113
+ @backend.should be_return_body(method, code, false)
114
+ @backend.should be_return_body(method, code.to_s, false)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ it "should force subclasses to implement the perform method" do
121
+ lambda { @backend.send(:perform, :get, "/foo", {}, 200) }.should raise_error(NotImplementedError)
122
+ end
123
+
124
+ it "should allow using the https protocol" do
125
+ @client = Riak::Client.new(:protocol => 'https')
126
+ @backend = Riak::Client::HTTPBackend.new(@client)
127
+ @backend.root_uri.to_s.should eq("https://127.0.0.1:8098")
128
+ end
129
+ end
@@ -76,7 +76,7 @@ describe Riak::Client::HTTPBackend do
76
76
  end
77
77
 
78
78
  it "should raise an exception when the response code is not 200 or 304" do
79
- @backend.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, ''))
79
+ @backend.should_receive(:get).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 500, {}, ''))
80
80
  lambda { @backend.reload_object(@object) }.should raise_error(Riak::FailedRequest)
81
81
  end
82
82
 
@@ -227,6 +227,18 @@ describe Riak::Client::HTTPBackend do
227
227
  @backend.stub!(:post).and_return(response)
228
228
  @backend.mapred(@mr).should == response
229
229
  end
230
+
231
+ it "should stream results through the block" do
232
+ data = File.read("spec/fixtures/multipart-mapreduce.txt")
233
+ @backend.should_receive(:post).with(200, "/mapred", {:chunked => true}, @mr.to_json, hash_including("Content-Type" => "application/json")).and_yield(data)
234
+ block = mock
235
+ block.should_receive(:ping).twice.and_return(true)
236
+ @backend.mapred(@mr) do |phase, data|
237
+ block.ping
238
+ phase.should == 0
239
+ data.should have(1).item
240
+ end
241
+ end
230
242
  end
231
243
 
232
244
  context "getting statistics" do
@@ -0,0 +1,45 @@
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
+ describe Riak::MapReduce::FilterBuilder do
17
+ subject { Riak::MapReduce::FilterBuilder.new }
18
+ it "should evaluate the passed block on initialization" do
19
+ subject.class.new do
20
+ matches "foo"
21
+ end.to_a.should == [[:matches, "foo"]]
22
+ end
23
+
24
+ it "should add filters to the list" do
25
+ subject.to_lower
26
+ subject.similar_to("ripple", 3)
27
+ subject.to_a.should == [[:to_lower],[:similar_to, "ripple", 3]]
28
+ end
29
+
30
+ it "should add a logical operation with a block" do
31
+ subject.OR do
32
+ starts_with "foo"
33
+ ends_with "bar"
34
+ end
35
+ subject.to_a.should == [[:or, [[:starts_with, "foo"],[:ends_with, "bar"]]]]
36
+ end
37
+
38
+ it "should raise an error on a filter arity mismatch" do
39
+ lambda { subject.less_than }.should raise_error(ArgumentError)
40
+ end
41
+
42
+ it "should raise an error when a block is not given to a logical operation" do
43
+ lambda { subject._or }.should raise_error(ArgumentError)
44
+ end
45
+ end
@@ -0,0 +1,149 @@
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
+ describe Riak::MapReduce::Phase do
17
+ before :each do
18
+ @fun = "function(v,_,_){ return v['values'][0]['data']; }"
19
+ end
20
+
21
+ it "should initialize with a type and a function" do
22
+ phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun, :language => "javascript")
23
+ phase.type.should == :map
24
+ phase.function.should == @fun
25
+ phase.language.should == "javascript"
26
+ end
27
+
28
+ it "should initialize with a type and an MF" do
29
+ phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"], :language => "erlang")
30
+ phase.type.should == :map
31
+ phase.function.should == ["module", "function"]
32
+ phase.language.should == "erlang"
33
+ end
34
+
35
+ it "should initialize with a type and a bucket/key" do
36
+ phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "funs", :key => "awesome_map"}, :language => "javascript")
37
+ phase.type.should == :map
38
+ phase.function.should == {:bucket => "funs", :key => "awesome_map"}
39
+ phase.language.should == "javascript"
40
+ end
41
+
42
+ it "should assume the language is erlang when the function is an array" do
43
+ phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"])
44
+ phase.language.should == "erlang"
45
+ end
46
+
47
+ it "should assume the language is javascript when the function is a string" do
48
+ phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun)
49
+ phase.language.should == "javascript"
50
+ end
51
+
52
+ it "should assume the language is javascript when the function is a hash" do
53
+ phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "jobs", :key => "awesome_map"})
54
+ phase.language.should == "javascript"
55
+ end
56
+
57
+ it "should accept a WalkSpec for the function when a link phase" do
58
+ phase = Riak::MapReduce::Phase.new(:type => :link, :function => Riak::WalkSpec.new({}))
59
+ phase.function.should be_kind_of(Riak::WalkSpec)
60
+ end
61
+
62
+ it "should raise an error if a WalkSpec is given for a phase type other than :link" do
63
+ lambda { Riak::MapReduce::Phase.new(:type => :map, :function => Riak::WalkSpec.new({})) }.should raise_error(ArgumentError)
64
+ end
65
+
66
+ describe "converting to JSON for the job" do
67
+ before :each do
68
+ @phase = Riak::MapReduce::Phase.new(:type => :map, :function => "")
69
+ end
70
+
71
+ [:map, :reduce].each do |type|
72
+ describe "when a #{type} phase" do
73
+ before :each do
74
+ @phase.type = type
75
+ end
76
+
77
+ it "should be an object with a single key of '#{type}'" do
78
+ @phase.to_json.should =~ /^\{"#{type}":/
79
+ end
80
+
81
+ it "should include the language" do
82
+ @phase.to_json.should =~ /"language":/
83
+ end
84
+
85
+ it "should include the keep value" do
86
+ @phase.to_json.should =~ /"keep":false/
87
+ @phase.keep = true
88
+ @phase.to_json.should =~ /"keep":true/
89
+ end
90
+
91
+ it "should include the function source when the function is a source string" do
92
+ @phase.function = "function(v,_,_){ return v; }"
93
+ @phase.to_json.should include(@phase.function)
94
+ @phase.to_json.should =~ /"source":/
95
+ end
96
+
97
+ it "should include the function name when the function is not a lambda" do
98
+ @phase.function = "Riak.mapValues"
99
+ @phase.to_json.should include('"name":"Riak.mapValues"')
100
+ @phase.to_json.should_not include('"source"')
101
+ end
102
+
103
+ it "should include the bucket and key when referring to a stored function" do
104
+ @phase.function = {:bucket => "design", :key => "wordcount_map"}
105
+ @phase.to_json.should include('"bucket":"design"')
106
+ @phase.to_json.should include('"key":"wordcount_map"')
107
+ end
108
+
109
+ it "should include the module and function when invoking an Erlang function" do
110
+ @phase.function = ["riak_mapreduce", "mapreduce_fun"]
111
+ @phase.to_json.should include('"module":"riak_mapreduce"')
112
+ @phase.to_json.should include('"function":"mapreduce_fun"')
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "when a link phase" do
118
+ before :each do
119
+ @phase.type = :link
120
+ @phase.function = {}
121
+ end
122
+
123
+ it "should be an object of a single key 'link'" do
124
+ @phase.to_json.should =~ /^\{"link":/
125
+ end
126
+
127
+ it "should include the bucket" do
128
+ @phase.to_json.should =~ /"bucket":"_"/
129
+ @phase.function[:bucket] = "foo"
130
+ @phase.to_json.should =~ /"bucket":"foo"/
131
+ end
132
+
133
+ it "should include the tag" do
134
+ @phase.to_json.should =~ /"tag":"_"/
135
+ @phase.function[:tag] = "parent"
136
+ @phase.to_json.should =~ /"tag":"parent"/
137
+ end
138
+
139
+ it "should include the keep value" do
140
+ @phase.to_json.should =~ /"keep":false/
141
+ @phase.keep = true
142
+ @phase.to_json.should =~ /"keep":true/
143
+ @phase.keep = false
144
+ @phase.function[:keep] = true
145
+ @phase.to_json.should =~ /"keep":true/
146
+ end
147
+ end
148
+ end
149
+ end