couchbase 1.1.5-x86-mingw32 → 1.2.0.beta-x86-mingw32

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 (52) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +12 -1
  3. data/HISTORY.markdown +112 -1
  4. data/README.markdown +149 -6
  5. data/couchbase.gemspec +5 -1
  6. data/ext/couchbase_ext/.gitignore +4 -0
  7. data/ext/couchbase_ext/arguments.c +973 -0
  8. data/ext/couchbase_ext/arithmetic.c +322 -0
  9. data/ext/couchbase_ext/bucket.c +1092 -0
  10. data/ext/couchbase_ext/couchbase_ext.c +618 -3247
  11. data/ext/couchbase_ext/couchbase_ext.h +519 -0
  12. data/ext/couchbase_ext/delete.c +167 -0
  13. data/ext/couchbase_ext/extconf.rb +24 -5
  14. data/ext/couchbase_ext/get.c +301 -0
  15. data/ext/couchbase_ext/gethrtime.c +124 -0
  16. data/ext/couchbase_ext/http.c +402 -0
  17. data/ext/couchbase_ext/observe.c +174 -0
  18. data/ext/couchbase_ext/result.c +126 -0
  19. data/ext/couchbase_ext/stats.c +169 -0
  20. data/ext/couchbase_ext/store.c +522 -0
  21. data/ext/couchbase_ext/timer.c +192 -0
  22. data/ext/couchbase_ext/touch.c +190 -0
  23. data/ext/couchbase_ext/unlock.c +180 -0
  24. data/ext/couchbase_ext/utils.c +471 -0
  25. data/ext/couchbase_ext/version.c +147 -0
  26. data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
  27. data/lib/active_support/cache/couchbase_store.rb +356 -0
  28. data/lib/couchbase.rb +24 -3
  29. data/lib/couchbase/bucket.rb +372 -9
  30. data/lib/couchbase/result.rb +26 -0
  31. data/lib/couchbase/utils.rb +59 -0
  32. data/lib/couchbase/version.rb +1 -1
  33. data/lib/couchbase/view.rb +305 -0
  34. data/lib/couchbase/view_row.rb +230 -0
  35. data/lib/ext/multi_json_fix.rb +47 -0
  36. data/lib/rack/session/couchbase.rb +104 -0
  37. data/tasks/compile.rake +5 -14
  38. data/test/setup.rb +6 -2
  39. data/test/test_arithmetic.rb +32 -2
  40. data/test/test_async.rb +18 -4
  41. data/test/test_bucket.rb +11 -1
  42. data/test/test_cas.rb +13 -3
  43. data/test/test_couchbase_rails_cache_store.rb +294 -0
  44. data/test/test_delete.rb +60 -3
  45. data/test/test_format.rb +28 -17
  46. data/test/test_get.rb +91 -14
  47. data/test/test_store.rb +31 -1
  48. data/test/{test_flush.rb → test_timer.rb} +11 -18
  49. data/test/test_touch.rb +33 -5
  50. data/test/test_unlock.rb +120 -0
  51. data/test/test_utils.rb +26 -0
  52. metadata +102 -12
@@ -0,0 +1,47 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011, 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ # This patch fixing loading primitives like strings, numbers and booleans
19
+ # using json_gem. It amends behaviour of MultiJson::Engines::JsonGem#load
20
+ # when it receives JSON string which neither Array nor Object.
21
+
22
+ if MultiJson.respond_to?(:engine)
23
+ multi_json_engine = MultiJson.send(:engine)
24
+ else
25
+ multi_json_engine = MultiJson.send(:adapter)
26
+ end
27
+ if multi_json_engine.name =~ /JsonGem$/
28
+ class << multi_json_engine
29
+ alias _load_object load
30
+ def load(string, options = {})
31
+ if string =~ /\A\s*[{\[]/
32
+ _load_object(string, options)
33
+ else
34
+ _load_object("[#{string}]", options)[0]
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # Patch for MultiJson versions < 1.3.3
41
+ if MultiJson.respond_to?(:decode) && MultiJson.respond_to?(:encode) &&
42
+ !MultiJson.respond_to?(:load) && !MultiJson.respond_to?(:dump)
43
+ class << MultiJson
44
+ alias :dump :encode
45
+ alias :load :decode
46
+ end
47
+ end
@@ -0,0 +1,104 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011, 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'rack/session/abstract/id'
19
+ require 'couchbase'
20
+ require 'thread'
21
+
22
+ module Rack
23
+ module Session
24
+
25
+ # This is Couchbase-powered session store for rack applications
26
+ #
27
+ # To use it just load it as usual middleware in your `config.ru` file
28
+ #
29
+ # require 'rack/session/couchbase'
30
+ # use Rack::Session::Couchbase
31
+ #
32
+ # You can also pass additional options:
33
+ #
34
+ # require 'rack/session/couchbase'
35
+ # use Rack::Session::Couchbase, :expire_after => 5.minutes,
36
+ # :couchbase => {:bucket => "sessions", :default_format => :marshal}
37
+ #
38
+ # By default sessions will be serialized to JSON, to allow analyse them
39
+ # using Map/Reduce.
40
+ #
41
+ class Couchbase < Abstract::ID
42
+ attr_reader :mutex, :pool
43
+
44
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge(
45
+ :couchbase => {:default_format => :document, :key_prefix => 'rack:session:'})
46
+
47
+ def initialize(app, options = {})
48
+ # Support old :expires option
49
+ options[:expire_after] ||= options[:expires]
50
+ super
51
+
52
+ @default_options[:couchbase][:default_ttl] ||= options[:expire_after]
53
+ @default_options[:couchbase][:key_prefix] ||= options[:namespace]
54
+ @namespace = @default_options[:couchbase][:key_prefix]
55
+ @mutex = Mutex.new
56
+ @pool = ::Couchbase.connect(@default_options[:couchbase])
57
+ end
58
+
59
+ def generate_sid
60
+ loop do
61
+ sid = super
62
+ break sid unless @pool.get(sid)
63
+ end
64
+ end
65
+
66
+ def get_session(env, sid)
67
+ with_lock(env, [nil, {}]) do
68
+ unless sid and session = @pool.get(sid)
69
+ sid, session = generate_sid, {}
70
+ @pool.set(sid, session)
71
+ end
72
+ [sid, session]
73
+ end
74
+ end
75
+
76
+ def set_session(env, session_id, new_session, options)
77
+ with_lock(env, false) do
78
+ @pool.set session_id, new_session, options
79
+ session_id
80
+ end
81
+ end
82
+
83
+ def destroy_session(env, session_id, options)
84
+ with_lock(env) do
85
+ @pool.delete(session_id)
86
+ generate_sid unless options[:drop]
87
+ end
88
+ end
89
+
90
+ def with_lock(env, default = nil)
91
+ @mutex.lock if env['rack.multithread']
92
+ yield
93
+ rescue Couchbase::Error::Connect, Couchbase::Error::Timeout
94
+ if $VERBOSE
95
+ warn "#{self} is unable to find Couchbase server."
96
+ warn $!.inspect
97
+ end
98
+ default
99
+ ensure
100
+ @mutex.unlock if @mutex.locked?
101
+ end
102
+ end
103
+ end
104
+ end
@@ -57,7 +57,6 @@ end
57
57
 
58
58
  require 'rubygems/package_task'
59
59
  Gem::PackageTask.new(gemspec) do |pkg|
60
- pkg.need_zip = true
61
60
  pkg.need_tar = true
62
61
  end
63
62
 
@@ -84,22 +83,14 @@ end
84
83
  namespace :ports do
85
84
  directory "ports"
86
85
 
87
- task :libvbucket => ["ports"] do
88
- recipe = MiniPortile.new "libvbucket", "1.8.0.3"
89
- recipe.files << "http://packages.couchbase.com/clients/c/#{recipe.name}-#{recipe.version}.tar.gz"
90
- recipe.configure_options.push("--disable-debug",
91
- "--without-docs",
92
- "--disable-dependency-tracking")
93
- recipe.cook
94
- recipe.activate
95
- end
96
-
97
- task :libcouchbase => [:libvbucket] do
98
- recipe = MiniPortile.new "libcouchbase", "1.0.6"
99
- recipe.files << "http://packages.couchbase.com/clients/c/#{recipe.name}-#{recipe.version}.tar.gz"
86
+ task :libcouchbase => ["ports"] do
87
+ recipe = MiniPortile.new "libcouchbase", "2.0.0beta_5_g970a292"
88
+ recipe.files << "http://packages.couchbase.com/clients/c/libcouchbase-#{recipe.version}.tar.gz"
100
89
  recipe.configure_options.push("--disable-debug",
101
90
  "--disable-dependency-tracking",
102
91
  "--disable-couchbasemock",
92
+ "--disable-cxx",
93
+ "--disable-examples",
103
94
  "--disable-tools")
104
95
  recipe.cook
105
96
  recipe.activate
@@ -36,7 +36,7 @@ class CouchbaseServer
36
36
  raise ArgumentError, "Check COUCHBASE_SERVER variable. It should be hostname:port"
37
37
  end
38
38
 
39
- @config = Yajl::Parser.parse(open("http://#{@host}:#{@port}/pools/default"))
39
+ @config = MultiJson.load(open("http://#{@host}:#{@port}/pools/default"))
40
40
  @num_nodes = @config["nodes"].size
41
41
  @buckets_spec = params[:buckets_spec] || "default:" # "default:,protected:secret,cache::memcache"
42
42
  end
@@ -50,7 +50,11 @@ class CouchbaseServer
50
50
  :username => name,
51
51
  :bucket => name,
52
52
  :password => password)
53
- connection.flush
53
+ begin
54
+ connection.flush
55
+ rescue Couchbase::Error::NotSupported
56
+ # on recent server flush is disabled
57
+ end
54
58
  end
55
59
  end
56
60
  def stop; end
@@ -84,7 +84,9 @@ class TestArithmetic < MiniTest::Unit::TestCase
84
84
  val = connection.incr(uniq_id(:missing), :create => true)
85
85
  assert_equal 1, val
86
86
  sleep(2)
87
- refute connection.get(uniq_id(:missing))
87
+ assert_raises(Couchbase::Error::NotFound) do
88
+ connection.get(uniq_id(:missing))
89
+ end
88
90
  end
89
91
 
90
92
  def test_decrement_with_absolute_ttl
@@ -95,7 +97,9 @@ class TestArithmetic < MiniTest::Unit::TestCase
95
97
  assert_equal 0, val
96
98
  assert_equal 0, connection.get(uniq_id)
97
99
  sleep(2)
98
- refute connection.get(uniq_id)
100
+ assert_raises(Couchbase::Error::NotFound) do
101
+ refute connection.get(uniq_id)
102
+ end
99
103
  end
100
104
 
101
105
  def test_it_allows_custom_delta
@@ -106,4 +110,30 @@ class TestArithmetic < MiniTest::Unit::TestCase
106
110
  assert_equal 22, val
107
111
  end
108
112
 
113
+ def test_it_allows_to_specify_delta_in_options
114
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
115
+
116
+ connection.set(uniq_id, 12)
117
+ options = {:delta => 10}
118
+ val = connection.incr(uniq_id, options)
119
+ assert_equal 22, val
120
+ end
121
+
122
+ def test_multi_incr
123
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
124
+ connection.set(uniq_id(:foo) => 1, uniq_id(:bar) => 1)
125
+
126
+ assert_equal [2, 2], connection.incr(uniq_id(:foo), uniq_id(:bar)).values.sort
127
+ assert_equal [12, 12], connection.incr(uniq_id(:foo), uniq_id(:bar), :delta => 10).values.sort
128
+ assert_equal [14, 15], connection.incr(uniq_id(:foo) => 2, uniq_id(:bar) => 3).values.sort
129
+ end
130
+
131
+ def test_multi_decr
132
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
133
+ connection.set(uniq_id(:foo) => 14, uniq_id(:bar) => 15)
134
+
135
+ assert_equal [12, 12], connection.decr(uniq_id(:foo) => 2, uniq_id(:bar) => 3).values.sort
136
+ assert_equal [2, 2], connection.decr(uniq_id(:foo), uniq_id(:bar), :delta => 10).values.sort
137
+ assert_equal [1, 1], connection.decr(uniq_id(:foo), uniq_id(:bar)).values.sort
138
+ end
109
139
  end
@@ -110,7 +110,9 @@ class TestAsync < MiniTest::Unit::TestCase
110
110
  assert success
111
111
  assert_equal "foo", val
112
112
  sleep(2)
113
- refute connection.get(uniq_id)
113
+ assert_raises(Couchbase::Error::NotFound) do
114
+ connection.get(uniq_id)
115
+ end
114
116
  end
115
117
 
116
118
  def test_nested_async_delete_get
@@ -167,7 +169,9 @@ class TestAsync < MiniTest::Unit::TestCase
167
169
  end
168
170
  end
169
171
 
170
- refute connection.get(uniq_id)
172
+ assert_raises(Couchbase::Error::NotFound) do
173
+ connection.get(uniq_id)
174
+ end
171
175
  res.keys.each do |key|
172
176
  assert res[key].is_a?(Numeric)
173
177
  assert connection.get(key)
@@ -222,13 +226,23 @@ class TestAsync < MiniTest::Unit::TestCase
222
226
  connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
223
227
  connection.set(uniq_id, "foo")
224
228
 
225
- connection.timeout = 100 # 100 us
229
+ connection.timeout = 100_000 # 100_000 us
226
230
  connection.run do
227
231
  connection.get(uniq_id) do |ret|
228
232
  assert ret.success?
229
233
  assert_equal "foo", ret.value
230
234
  end
231
- sleep(1.5)
235
+ sleep(1.5) # 1_500_000 us
236
+ end
237
+ end
238
+
239
+ def test_send_threshold
240
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
241
+
242
+ sent = false
243
+ connection.run(:send_threshold => 100) do # 100 bytes
244
+ connection.set(uniq_id, "foo" * 100) {|r| sent = true}
245
+ assert sent
232
246
  end
233
247
  end
234
248
 
@@ -151,7 +151,7 @@ class TestBucket < MiniTest::Unit::TestCase
151
151
  with_mock do |mock|
152
152
  connection = Couchbase.new(:hostname => mock.host,
153
153
  :port => mock.port)
154
- assert connection.quiet?
154
+ refute connection.quiet?
155
155
 
156
156
  connection = Couchbase.new(:hostname => mock.host,
157
157
  :port => mock.port,
@@ -224,4 +224,14 @@ class TestBucket < MiniTest::Unit::TestCase
224
224
  end
225
225
  end
226
226
 
227
+ def test_it_uses_bucket_name_as_username_if_username_is_empty
228
+ with_mock(:buckets_spec => 'protected:secret') do |mock|
229
+ connection = Couchbase.new(:hostname => mock.host,
230
+ :port => mock.port,
231
+ :bucket => 'protected',
232
+ :password => 'secret')
233
+ assert connection.connected?
234
+ end
235
+ end
236
+
227
237
  end
@@ -44,13 +44,23 @@ class TestCas < MiniTest::Unit::TestCase
44
44
  connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port,
45
45
  :default_format => :document)
46
46
  connection.set(uniq_id, {"bar" => 1})
47
+ calls = 0
47
48
  connection.run do |conn|
48
49
  conn.cas(uniq_id) do |ret|
49
- new_val = ret.value
50
- new_val["baz"] = 2
51
- new_val
50
+ calls += 1
51
+ case ret.operation
52
+ when :get
53
+ new_val = ret.value
54
+ new_val["baz"] = 2
55
+ new_val
56
+ when :set
57
+ assert ret.success?
58
+ else
59
+ flunk "Unexpected operation: #{ret.operation.inspect}"
60
+ end
52
61
  end
53
62
  end
63
+ assert_equal 2, calls
54
64
  val = connection.get(uniq_id)
55
65
  expected = {"bar" => 1, "baz" => 2}
56
66
  assert_equal expected, val
@@ -0,0 +1,294 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011, 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require File.join(File.dirname(__FILE__), 'setup')
19
+ require 'active_support/cache/couchbase_store'
20
+ require 'active_support/notifications'
21
+ require 'ostruct'
22
+
23
+ class TestCouchbaseRailsCacheStore < MiniTest::Unit::TestCase
24
+
25
+ def setup
26
+ @mock = start_mock
27
+ @foo = OpenStruct.new :payload => "foo"
28
+ @foobar = OpenStruct.new :payload => "foobar"
29
+ end
30
+
31
+ def teardown
32
+ stop_mock(@mock)
33
+ end
34
+
35
+ def store
36
+ @store ||= ActiveSupport::Cache::CouchbaseStore.new(:hostname => @mock.host,
37
+ :port => @mock.port)
38
+ end
39
+
40
+ def test_it_supported_methods
41
+ supported_methods = store.public_methods(false).map(&:to_sym)
42
+ assert supported_methods.include?(:fetch)
43
+ assert supported_methods.include?(:write)
44
+ assert supported_methods.include?(:read)
45
+ assert supported_methods.include?(:read_multi)
46
+ assert supported_methods.include?(:increment)
47
+ assert supported_methods.include?(:decrement)
48
+ assert supported_methods.include?(:exists?)
49
+ assert supported_methods.include?(:delete)
50
+ assert supported_methods.include?(:stats)
51
+ refute supported_methods.include?(:clear)
52
+ assert_raises(NotImplementedError) do
53
+ store.clear
54
+ end
55
+ refute supported_methods.include?(:cleanup)
56
+ assert_raises(NotImplementedError) do
57
+ store.cleanup
58
+ end
59
+ end
60
+
61
+ def test_it_writes_and_reads_the_data
62
+ store.write uniq_id, @foobar
63
+ assert_equal @foobar, store.read(uniq_id)
64
+ end
65
+
66
+ def test_it_writes_the_data_with_expiration_time
67
+ store.write(uniq_id, @foobar, :expires_in => 1.second)
68
+ assert_equal @foobar, store.read(uniq_id)
69
+ sleep 2
70
+ refute store.read(uniq_id)
71
+ end
72
+
73
+ def test_it_doest_write_data_if_unless_exist_option_is_true
74
+ store.write uniq_id, @foo
75
+ [:unless_exist, :unless_exists].each do |unless_exists|
76
+ store.write uniq_id, @foobar, unless_exists => true
77
+ assert_equal @foo, store.read(uniq_id)
78
+ end
79
+ end
80
+
81
+ if RUBY_VERSION.match /1\.9/
82
+ def test_it_reads_raw_data
83
+ store.write uniq_id, @foo
84
+ assert_equal "\x04\bU:\x0FOpenStruct{\x06:\fpayloadI\"\bfoo\x06:\x06EF",
85
+ store.read(uniq_id, :raw => true)
86
+ end
87
+ else
88
+ def test_it_reads_raw_data
89
+ store.write uniq_id, @foo
90
+ assert_equal "\004\bU:\017OpenStruct{\006:\fpayload\"\bfoo",
91
+ store.read(uniq_id, :raw => true)
92
+ end
93
+ end
94
+
95
+ def test_it_writes_raw_data
96
+ store.write uniq_id, @foobar, :raw => true
97
+ assert_equal '#<OpenStruct payload="foobar">', store.read(uniq_id, :raw => true)
98
+ end
99
+
100
+ def test_it_deletes_data
101
+ store.write uniq_id, @foo
102
+ store.delete uniq_id
103
+ refute store.read(uniq_id)
104
+ end
105
+
106
+ def test_it_verifies_existence_of_an_object_in_the_store
107
+ store.write uniq_id, @foo
108
+ assert store.exist?(uniq_id)
109
+ refute store.exist?(uniq_id(:missing))
110
+ end
111
+
112
+ def test_it_initializes_key_on_first_increment_with_zero
113
+ store.increment(uniq_id)
114
+ assert_equal 0, store.read(uniq_id)
115
+ assert_equal "0", store.read(uniq_id, :raw => true)
116
+ end
117
+
118
+ def test_it_initializes_key_on_first_decrement_with_zero
119
+ store.decrement(uniq_id)
120
+ assert_equal 0, store.read(uniq_id)
121
+ assert_equal "0", store.read(uniq_id, :raw => true)
122
+ end
123
+
124
+ def test_it_initializes_key_with_given_value_on_increment
125
+ store.increment(uniq_id, 1, :initial => 5)
126
+ assert_equal 5, store.read(uniq_id)
127
+ assert_equal "5", store.read(uniq_id, :raw => true)
128
+ end
129
+
130
+ def test_it_initializes_key_with_given_value_on_decrement
131
+ store.decrement(uniq_id, 1, :initial => 5)
132
+ assert_equal 5, store.read(uniq_id)
133
+ assert_equal "5", store.read(uniq_id, :raw => true)
134
+ end
135
+
136
+ def test_it_increments_a_key
137
+ 3.times { store.increment uniq_id }
138
+ assert_equal 2, store.read(uniq_id)
139
+ assert_equal "2", store.read(uniq_id, :raw => true)
140
+ end
141
+
142
+ def test_it_decrements_a_key
143
+ 4.times { store.increment uniq_id }
144
+ 2.times { store.decrement uniq_id }
145
+ assert_equal 1, store.read(uniq_id)
146
+ assert_equal "1", store.read(uniq_id, :raw => true)
147
+ end
148
+
149
+ def test_it_increments_a_raw_key
150
+ assert store.write(uniq_id, 1, :raw => true)
151
+ store.increment(uniq_id, 2)
152
+ assert_equal 3, store.read(uniq_id, :raw => true).to_i
153
+ end
154
+
155
+ def test_it_decrements_a_raw_key
156
+ assert store.write(uniq_id, 3, :raw => true)
157
+ store.decrement(uniq_id, 2)
158
+ assert_equal 1, store.read(uniq_id, :raw => true).to_i
159
+ end
160
+
161
+ def test_it_increments_a_key_by_given_value
162
+ store.write(uniq_id, 0, :raw => true)
163
+ store.increment uniq_id, 3
164
+ assert_equal 3, store.read(uniq_id, :raw => true).to_i
165
+ end
166
+
167
+ def test_it_decrements_a_key_by_given_value
168
+ store.write(uniq_id, 0, :raw => true)
169
+ 3.times { store.increment uniq_id }
170
+ store.decrement uniq_id, 2
171
+ assert_equal 1, store.read(uniq_id, :raw => true).to_i
172
+ end
173
+
174
+ def test_it_provides_store_stats
175
+ refute store.stats.empty?
176
+ end
177
+
178
+ def test_it_fetches_data
179
+ assert store.write(uniq_id, @foo)
180
+ assert_equal @foo, store.fetch(uniq_id)
181
+ refute store.fetch("rub-a-dub")
182
+ store.fetch("rub-a-dub") { "Flora de Cana" }
183
+ assert_equal "Flora de Cana", store.fetch("rub-a-dub")
184
+ store.fetch(uniq_id, :force => true) # force cache miss
185
+ store.fetch(uniq_id, :force => true, :expires_in => 1.second) { @foobar }
186
+ assert_equal @foobar, store.fetch(uniq_id)
187
+ sleep 2
188
+ refute store.fetch(uniq_id)
189
+ end
190
+
191
+ def test_it_reads_multiple_keys
192
+ assert store.write(uniq_id(1), @foo)
193
+ assert store.write(uniq_id(2), "foo")
194
+ result = store.read_multi uniq_id(1), uniq_id(2)
195
+ assert_equal @foo, result[uniq_id(1)]
196
+ assert_equal "foo", result[uniq_id(2)]
197
+ end
198
+
199
+ def test_it_reads_multiple_keys_and_returns_only_the_matched_ones
200
+ assert store.write(uniq_id, @foo)
201
+ result = store.read_multi uniq_id, uniq_id(:missing)
202
+ assert result[uniq_id]
203
+ refute result[uniq_id(:missing)]
204
+ end
205
+
206
+ def test_it_notifies_on_fetch
207
+ collect_notifications do
208
+ store.fetch(uniq_id) { "foo" }
209
+ end
210
+
211
+ read, generate, write = @events
212
+
213
+ assert_equal 'cache_read.active_support', read.name
214
+ assert_equal({:key => uniq_id, :super_operation => :fetch}, read.payload)
215
+
216
+ assert_equal 'cache_generate.active_support', generate.name
217
+ assert_equal({:key => uniq_id}, generate.payload)
218
+
219
+ assert_equal 'cache_write.active_support', write.name
220
+ assert_equal({:key => uniq_id}, write.payload)
221
+ end
222
+
223
+ def test_it_notifies_on_read
224
+ collect_notifications do
225
+ store.read uniq_id
226
+ end
227
+
228
+ read = @events.first
229
+ assert_equal 'cache_read.active_support', read.name
230
+ assert_equal({:key => uniq_id, :hit => false}, read.payload)
231
+ end
232
+
233
+ def test_it_notifies_on_write
234
+ collect_notifications do
235
+ store.write uniq_id, "foo"
236
+ end
237
+
238
+ write = @events.first
239
+ assert_equal 'cache_write.active_support', write.name
240
+ assert_equal({:key => uniq_id}, write.payload)
241
+ end
242
+
243
+ def test_it_notifies_on_delete
244
+ collect_notifications do
245
+ store.delete uniq_id
246
+ end
247
+
248
+ delete = @events.first
249
+ assert_equal 'cache_delete.active_support', delete.name
250
+ assert_equal({:key => uniq_id}, delete.payload)
251
+ end
252
+
253
+ def test_it_notifies_on_exist?
254
+ collect_notifications do
255
+ store.exist? uniq_id
256
+ end
257
+
258
+ exist = @events.first
259
+ assert_equal 'cache_exists?.active_support', exist.name
260
+ assert_equal({:key => uniq_id}, exist.payload)
261
+ end
262
+
263
+ def test_it_notifies_on_increment
264
+ collect_notifications do
265
+ store.increment uniq_id
266
+ end
267
+
268
+ increment = @events.first
269
+ assert_equal 'cache_increment.active_support', increment.name
270
+ assert_equal({:key => uniq_id, :amount => 1, :create => true}, increment.payload)
271
+ end
272
+
273
+ def test_it_notifies_on_decrement
274
+ collect_notifications do
275
+ store.decrement uniq_id
276
+ end
277
+
278
+ decrement = @events.first
279
+ assert_equal 'cache_decrement.active_support', decrement.name
280
+ assert_equal({:key => uniq_id, :amount => 1, :create => true}, decrement.payload)
281
+ end
282
+
283
+ private
284
+
285
+ def collect_notifications
286
+ @events = [ ]
287
+ ActiveSupport::Cache::CouchbaseStore.instrument = true
288
+ ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
289
+ @events << ActiveSupport::Notifications::Event.new(*args)
290
+ end
291
+ yield
292
+ ActiveSupport::Cache::CouchbaseStore.instrument = false
293
+ end
294
+ end