mongo 1.2.4 → 1.3.0.rc0

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 (46) hide show
  1. data/README.md +13 -25
  2. data/Rakefile +9 -1
  3. data/docs/HISTORY.md +19 -0
  4. data/docs/RELEASES.md +33 -0
  5. data/docs/REPLICA_SETS.md +4 -3
  6. data/lib/mongo.rb +20 -2
  7. data/lib/mongo/collection.rb +15 -2
  8. data/lib/mongo/connection.rb +75 -14
  9. data/lib/mongo/cursor.rb +12 -4
  10. data/lib/mongo/db.rb +3 -3
  11. data/lib/mongo/exceptions.rb +3 -0
  12. data/lib/mongo/gridfs/grid_io.rb +88 -7
  13. data/lib/mongo/repl_set_connection.rb +29 -11
  14. data/lib/mongo/util/pool.rb +15 -6
  15. data/lib/mongo/util/timeout.rb +42 -0
  16. data/lib/mongo/util/uri_parser.rb +5 -1
  17. data/test/auxillary/fork_test.rb +30 -0
  18. data/test/bson/bson_test.rb +68 -27
  19. data/test/bson/byte_buffer_test.rb +11 -0
  20. data/test/bson/object_id_test.rb +14 -1
  21. data/test/bson/ordered_hash_test.rb +7 -0
  22. data/test/bson/timestamp_test.rb +24 -0
  23. data/test/collection_test.rb +41 -24
  24. data/test/connection_test.rb +33 -2
  25. data/test/conversions_test.rb +10 -11
  26. data/test/cursor_fail_test.rb +1 -1
  27. data/test/cursor_message_test.rb +1 -1
  28. data/test/cursor_test.rb +33 -4
  29. data/test/db_api_test.rb +13 -2
  30. data/test/db_test.rb +3 -3
  31. data/test/grid_file_system_test.rb +0 -1
  32. data/test/grid_io_test.rb +72 -1
  33. data/test/grid_test.rb +16 -16
  34. data/test/replica_sets/connect_test.rb +8 -0
  35. data/test/replica_sets/query_test.rb +10 -0
  36. data/test/support/hash_with_indifferent_access.rb +0 -13
  37. data/test/support_test.rb +0 -1
  38. data/test/test_helper.rb +27 -8
  39. data/test/timeout_test.rb +14 -0
  40. data/test/unit/collection_test.rb +1 -1
  41. data/test/unit/connection_test.rb +0 -13
  42. data/test/unit/cursor_test.rb +16 -6
  43. data/test/unit/db_test.rb +9 -11
  44. data/test/unit/repl_set_connection_test.rb +0 -13
  45. data/test/unit/safe_test.rb +1 -1
  46. metadata +15 -23
@@ -69,7 +69,7 @@ module Mongo
69
69
  # the factory should not inject a new key).
70
70
  #
71
71
  # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
72
- # propogated to Collection objects instantiated off of this DB. If no
72
+ # propagated to Collection objects instantiated off of this DB. If no
73
73
  # value is provided, the default value set on this instance's Connection object will be used. This
74
74
  # default can be overridden upon instantiation of any collection by explicity setting a :safe value
75
75
  # on initialization
@@ -272,7 +272,7 @@ module Mongo
272
272
  if strict?
273
273
  raise MongoDBError, "Collection #{name} already exists. Currently in strict mode."
274
274
  else
275
- return Collection.new(name, self)
275
+ return Collection.new(name, self, opts)
276
276
  end
277
277
  end
278
278
 
@@ -287,7 +287,7 @@ module Mongo
287
287
  # Get a collection by name.
288
288
  #
289
289
  # @param [String] name the collection name.
290
- # @param [Hash] opts any valid options that can me passed to Collection#new.
290
+ # @param [Hash] opts any valid options that can be passed to Collection#new.
291
291
  #
292
292
  # @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
293
293
  #
@@ -57,6 +57,9 @@ module Mongo
57
57
  # Raised when a database operation fails.
58
58
  class OperationFailure < MongoDBError; end
59
59
 
60
+ # Raised when a socket read operation times out.
61
+ class OperationTimeout < ::Timeout::Error; end
62
+
60
63
  # Raised when a client attempts to perform an invalid operation.
61
64
  class InvalidOperation < MongoDBError; end
62
65
 
@@ -17,10 +17,6 @@
17
17
  # ++
18
18
 
19
19
  require 'digest/md5'
20
- begin
21
- require 'mime/types'
22
- rescue LoadError
23
- end
24
20
 
25
21
  module Mongo
26
22
 
@@ -172,6 +168,91 @@ module Mongo
172
168
  def tell
173
169
  @file_position
174
170
  end
171
+ alias :pos :tell
172
+
173
+ # Rewind the file. This is equivalent to seeking to the zeroth position.
174
+ #
175
+ # @return [Integer] the position of the file after rewinding (always zero).
176
+ def rewind
177
+ raise GridError, "file not opened for read" unless @mode[0] == ?r
178
+ seek(0)
179
+ end
180
+
181
+ # Return a boolean indicating whether the position pointer is
182
+ # at the end of the file.
183
+ #
184
+ # @return [Boolean]
185
+ def eof
186
+ raise GridError, "file not opened for read #{@mode}" unless @mode[0] == ?r
187
+ @file_position >= @file_length
188
+ end
189
+ alias :eof? :eof
190
+
191
+ # Return the next line from a GridFS file. This probably
192
+ # makes sense only if you're storing plain text. This method
193
+ # has a somewhat tricky API, which it inherits from Ruby's
194
+ # StringIO#gets.
195
+ #
196
+ # @param [String, Integer] separator or length. If a separator,
197
+ # read up to the separator. If a length, read the +length+ number
198
+ # of bytes. If nil, read the entire file.
199
+ # @param [Integer] length If a separator is provided, then
200
+ # read until either finding the separator or
201
+ # passing over the +length+ number of bytes.
202
+ #
203
+ # @return [String]
204
+ def gets(separator="\n", length=nil)
205
+ if separator.nil?
206
+ read_all
207
+ elsif separator.is_a?(Integer)
208
+ read_length(separator)
209
+ elsif separator.length > 1
210
+ result = ''
211
+ len = 0
212
+ match_idx = 0
213
+ match_num = separator.length - 1
214
+ to_match = separator[match_idx].chr
215
+ if length
216
+ matcher = lambda {|idx, num| idx < num && len < length }
217
+ else
218
+ matcher = lambda {|idx, num| idx < num}
219
+ end
220
+ while matcher.call(match_idx, match_num) && char = getc
221
+ result << char
222
+ len += 1
223
+ if char == to_match
224
+ while match_idx < match_num do
225
+ match_idx += 1
226
+ to_match = separator[match_idx].chr
227
+ char = getc
228
+ result << char
229
+ if char != to_match
230
+ match_idx = 0
231
+ to_match = separator[match_idx].chr
232
+ break
233
+ end
234
+ end
235
+ end
236
+ end
237
+ result
238
+ else
239
+ result = ''
240
+ len = 0
241
+ while char = getc
242
+ result << char
243
+ len += 1
244
+ break if char == separator || (length ? len >= length : false)
245
+ end
246
+ result
247
+ end
248
+ end
249
+
250
+ # Return the next byte from the GridFS file.
251
+ #
252
+ # @return [String]
253
+ def getc
254
+ read_length(1)
255
+ end
175
256
 
176
257
  # Creates or updates the document from the files collection that
177
258
  # stores the chunks' metadata. The file becomes available only after
@@ -260,7 +341,7 @@ module Mongo
260
341
  if length.nil?
261
342
  to_read = remaining
262
343
  else
263
- to_read = length > remaining ? remaining : length
344
+ to_read = length > remaining ? remaining : length
264
345
  end
265
346
  return nil unless remaining > 0
266
347
 
@@ -335,8 +416,8 @@ module Mongo
335
416
  @files_id = opts.delete(:_id) || BSON::ObjectId.new
336
417
  @content_type = opts.delete(:content_type) || (defined? MIME) && get_content_type || DEFAULT_CONTENT_TYPE
337
418
  @chunk_size = opts.delete(:chunk_size) || DEFAULT_CHUNK_SIZE
338
- @metadata = opts.delete(:metadata) if opts[:metadata]
339
- @aliases = opts.delete(:aliases) if opts[:aliases]
419
+ @metadata = opts.delete(:metadata)
420
+ @aliases = opts.delete(:aliases)
340
421
  @file_length = 0
341
422
  opts.each {|k, v| self[k] = v}
342
423
  check_existing_file if @safe
@@ -29,8 +29,8 @@ module Mongo
29
29
  # Connection#arbiters. This is useful if your application needs to connect manually to nodes other
30
30
  # than the primary.
31
31
  #
32
- # @param [Array] args A list of host-port pairs ending with a hash containing any options. See
33
- # the examples below for exactly how to use the constructor.
32
+ # @param [Array] args A list of host-port pairs to be used as seed nodes followed by a
33
+ # hash containing any options. See the examples below for exactly how to use the constructor.
34
34
  #
35
35
  # @option options [String] :rs_name (nil) The name of the replica set to connect to. You
36
36
  # can use this option to verify that you're connecting to the right replica set.
@@ -47,7 +47,9 @@ module Mongo
47
47
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
48
48
  # Note: this setting is relevant only for multi-threaded applications.
49
49
  #
50
- # @example Connect to a replica set and provide two seed nodes:
50
+ # @example Connect to a replica set and provide two seed nodes. Note that the number of seed nodes does
51
+ # not have to be equal to the number of replica set members. The purpose of seed nodes is to permit
52
+ # the driver to find at least one replica set member even if a member is down.
51
53
  # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
52
54
  #
53
55
  # @example Connect to a replica set providing two seed nodes and ensuring a connection to the replica set named 'prod':
@@ -99,7 +101,7 @@ module Mongo
99
101
  #
100
102
  # @raise [ConnectionFailure] if unable to connect to any host or port.
101
103
  def connect
102
- reset_connection
104
+ close
103
105
  @nodes_to_try = @nodes.clone
104
106
 
105
107
  while connecting?
@@ -133,6 +135,20 @@ module Mongo
133
135
  @nodes_to_try.length > 0
134
136
  end
135
137
 
138
+ # The replica set primary's host name.
139
+ #
140
+ # @return [String]
141
+ def host
142
+ super
143
+ end
144
+
145
+ # The replica set primary's port.
146
+ #
147
+ # @return [Integer]
148
+ def port
149
+ super
150
+ end
151
+
136
152
  # Determine whether we're reading from a primary node. If false,
137
153
  # this connection connects to a secondary node and @read_secondaries is true.
138
154
  #
@@ -149,13 +165,6 @@ module Mongo
149
165
  @secondary_pools.each do |pool|
150
166
  pool.close
151
167
  end
152
- end
153
-
154
- # If a ConnectionFailure is raised, this method will be called
155
- # to close the connection and reset connection values.
156
- # TODO: what's the point of this method?
157
- def reset_connection
158
- super
159
168
  @secondaries = []
160
169
  @secondary_pools = []
161
170
  @arbiters = []
@@ -163,6 +172,15 @@ module Mongo
163
172
  @nodes_to_try = []
164
173
  end
165
174
 
175
+ # If a ConnectionFailure is raised, this method will be called
176
+ # to close the connection and reset connection values.
177
+ # @deprecated
178
+ def reset_connection
179
+ close
180
+ warn "ReplSetConnection#reset_connection is now deprecated. " +
181
+ "Use ReplSetConnection#close instead."
182
+ end
183
+
166
184
  # Is it okay to connect to a slave?
167
185
  #
168
186
  # @return [Boolean]
@@ -41,6 +41,7 @@ module Mongo
41
41
  @socket_ops = Hash.new { |h, k| h[k] = [] }
42
42
 
43
43
  @sockets = []
44
+ @pids = {}
44
45
  @checked_out = []
45
46
  end
46
47
 
@@ -54,6 +55,7 @@ module Mongo
54
55
  end
55
56
  @host = @port = nil
56
57
  @sockets.clear
58
+ @pids.clear
57
59
  @checked_out.clear
58
60
  end
59
61
 
@@ -83,6 +85,7 @@ module Mongo
83
85
  @connection.apply_saved_authentication(:socket => socket)
84
86
 
85
87
  @sockets << socket
88
+ @pids[socket] = Process.pid
86
89
  @checked_out << socket
87
90
  socket
88
91
  end
@@ -115,12 +118,22 @@ module Mongo
115
118
 
116
119
  # Checks out the first available socket from the pool.
117
120
  #
121
+ # If the pid has changed, remove the socket and check out
122
+ # new one.
123
+ #
118
124
  # This method is called exclusively from #checkout;
119
125
  # therefore, it runs within a mutex.
120
126
  def checkout_existing_socket
121
127
  socket = (@sockets - @checked_out).first
122
- @checked_out << socket
123
- socket
128
+ if @pids[socket] != Process.pid
129
+ @pids[socket] = nil
130
+ @sockets.delete(socket)
131
+ socket.close
132
+ checkout_new_socket
133
+ else
134
+ @checked_out << socket
135
+ socket
136
+ end
124
137
  end
125
138
 
126
139
  # Check out an existing socket or create a new socket if the maximum
@@ -155,10 +168,6 @@ module Mongo
155
168
  return socket
156
169
  else
157
170
  # Otherwise, wait
158
- if @logger
159
- @logger.warn "MONGODB Waiting for available connection; " +
160
- "#{@checked_out.size} of #{@size} connections checked out."
161
- end
162
171
  @queue.wait(@connection_mutex)
163
172
  end
164
173
  end
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+ module Mongo #:nodoc:
19
+ module TimeoutWrapper
20
+ extend self
21
+
22
+ def timeout_lib=(lib)
23
+ @@timeout_lib = lib
24
+ end
25
+
26
+ def timeout_lib
27
+ @@timeout_lib
28
+ end
29
+
30
+ def timeout(delay, &block)
31
+ if timeout_lib
32
+ begin
33
+ timeout_lib.timeout(delay, &block)
34
+ rescue ::Timeout::Error
35
+ raise Mongo::OperationTimeout
36
+ end
37
+ else
38
+ yield
39
+ end
40
+ end
41
+ end
42
+ end
@@ -20,7 +20,7 @@ module Mongo
20
20
  class URIParser
21
21
 
22
22
  DEFAULT_PORT = 27017
23
- MONGODB_URI_MATCHER = /(([-_.\w\d]+):([^@]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
23
+ MONGODB_URI_MATCHER = /(([-.\w]+):([^@]+)@)?([-.\w]+)(:([\w]+))?(\/([-\w]+))?/
24
24
  MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
25
25
  SPEC_ATTRS = [:nodes, :auths]
26
26
  OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync]
@@ -142,7 +142,11 @@ module Mongo
142
142
  # This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
143
143
  # and convert the given options.
144
144
  def parse_options(opts)
145
+ # initialize instance variables for available options
146
+ OPT_VALID.keys.each { |k| instance_variable_set("@#{k}", nil) }
147
+
145
148
  return unless opts
149
+
146
150
  separator = opts.include?('&') ? '&' : ';'
147
151
  opts.split(separator).each do |attr|
148
152
  key, value = attr.split('=')
@@ -0,0 +1,30 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'mongo'
3
+ require 'test/unit'
4
+ require './test/test_helper'
5
+
6
+ class ForkTest < Test::Unit::TestCase
7
+ include Mongo
8
+
9
+ def setup
10
+ @conn = standard_connection
11
+ end
12
+
13
+ def test_fork
14
+ # Now insert some data
15
+ 10.times do |n|
16
+ @conn[MONGO_TEST_DB]['nums'].insert({:a => n})
17
+ end
18
+
19
+ # Now fork. You'll almost always see an exception here.
20
+ if !Kernel.fork
21
+ 10.times do
22
+ assert @conn[MONGO_TEST_DB]['nums'].find_one
23
+ end
24
+ else
25
+ 10.times do
26
+ assert @conn[MONGO_TEST_DB]['nums'].find_one
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,10 +1,14 @@
1
1
  # encoding:utf-8
2
2
  require './test/test_helper'
3
- require 'complex'
3
+
4
+ if RUBY_VERSION < '1.9'
5
+ require 'complex'
6
+ require 'rational'
7
+ end
4
8
  require 'bigdecimal'
5
- require 'rational'
6
9
 
7
10
  begin
11
+ require 'tzinfo'
8
12
  require 'active_support/core_ext'
9
13
  Time.zone = "Pacific Time (US & Canada)"
10
14
  Zone = Time.zone.now
@@ -116,15 +120,23 @@ class BSONTest < Test::Unit::TestCase
116
120
  end
117
121
  else
118
122
  def test_non_utf8_string
119
- bson = BSON::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')})
120
- result = BSON::BSON_CODER.deserialize(bson)['str']
121
- assert_equal 'aé', result
122
- assert_equal 'UTF-8', result.encoding.name
123
+ assert_raise BSON::InvalidStringEncoding do
124
+ BSON::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')})
125
+ end
126
+ end
127
+
128
+ def test_invalid_utf8_string
129
+ str = "123\xD9"
130
+ assert !str.valid_encoding?
131
+ assert_raise BSON::InvalidStringEncoding do
132
+ BSON::BSON_CODER.serialize({'str' => str})
133
+ end
123
134
  end
124
135
 
125
136
  def test_non_utf8_key
126
- bson = BSON::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'})
127
- assert_equal 'hello', BSON::BSON_CODER.deserialize(bson)['aé']
137
+ assert_raise BSON::InvalidStringEncoding do
138
+ BSON::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'})
139
+ end
128
140
  end
129
141
 
130
142
  # Based on a test from sqlite3-ruby
@@ -134,14 +146,14 @@ class BSONTest < Test::Unit::TestCase
134
146
  str = "壁に耳あり、障子に目あり"
135
147
  bson = BSON::BSON_CODER.serialize("x" => str)
136
148
 
137
- Encoding.default_internal = 'EUC-JP'
149
+ silently { Encoding.default_internal = 'EUC-JP' }
138
150
  out = BSON::BSON_CODER.deserialize(bson)["x"]
139
151
 
140
152
  assert_equal Encoding.default_internal, out.encoding
141
153
  assert_equal str.encode('EUC-JP'), out
142
154
  assert_equal str, out.encode(str.encoding)
143
155
  ensure
144
- Encoding.default_internal = before_enc
156
+ silently { Encoding.default_internal = before_enc }
145
157
  end
146
158
  end
147
159
 
@@ -161,12 +173,12 @@ class BSONTest < Test::Unit::TestCase
161
173
  assert_doc_pass(doc)
162
174
  end
163
175
 
164
- def test_double
165
- doc = {'doc' => 41.25}
166
- assert_doc_pass(doc)
167
- end
176
+ def test_double
177
+ doc = {'doc' => 41.25}
178
+ assert_doc_pass(doc)
179
+ end
168
180
 
169
- def test_int
181
+ def test_int
170
182
  doc = {'doc' => 42}
171
183
  assert_doc_pass(doc)
172
184
 
@@ -246,8 +258,14 @@ class BSONTest < Test::Unit::TestCase
246
258
  assert_in_delta doc['date'], doc2['date'], 0.001
247
259
  end
248
260
 
261
+ def test_date_in_array
262
+ doc = {'date' => [Time.now.utc]}
263
+ bson = @encoder.serialize(doc)
264
+ doc2 = @encoder.deserialize(bson)
265
+ end
266
+
249
267
  def test_date_returns_as_utc
250
- doc = {'date' => Time.now}
268
+ doc = {'date' => Time.now.utc}
251
269
  bson = @encoder.serialize(doc)
252
270
  doc2 = @encoder.deserialize(bson)
253
271
  assert doc2['date'].utc?
@@ -277,7 +295,7 @@ class BSONTest < Test::Unit::TestCase
277
295
  ensure
278
296
  if !invalid_date.is_a? Time
279
297
  assert_equal InvalidDocument, e.class
280
- assert_match /UTC Time/, e.message
298
+ assert_match(/UTC Time/, e.message)
281
299
  end
282
300
  end
283
301
  end
@@ -311,6 +329,20 @@ class BSONTest < Test::Unit::TestCase
311
329
  bin = Binary.new
312
330
  'binstring'.each_byte { |b| bin.put(b) }
313
331
 
332
+ doc = {'bin' => bin}
333
+ bson = @encoder.serialize(doc)
334
+ doc2 = @encoder.deserialize(bson)
335
+ bin2 = doc2['bin']
336
+ assert_kind_of Binary, bin2
337
+ assert_equal 'binstring', bin2.to_s
338
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
339
+ end
340
+
341
+ def test_binary_with_deprecated_subtype
342
+ bin = Binary.new
343
+ 'binstring'.each_byte { |b| bin.put(b) }
344
+ bin.subtype = Binary::SUBTYPE_BYTES
345
+
314
346
  doc = {'bin' => bin}
315
347
  bson = @encoder.serialize(doc)
316
348
  doc2 = @encoder.deserialize(bson)
@@ -328,7 +360,7 @@ class BSONTest < Test::Unit::TestCase
328
360
  bin2 = doc2['bin']
329
361
  assert_kind_of Binary, bin2
330
362
  assert_equal 'somebinarystring', bin2.to_s
331
- assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
363
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
332
364
  end
333
365
 
334
366
  def test_binary_type
@@ -368,7 +400,7 @@ class BSONTest < Test::Unit::TestCase
368
400
  bin2 = doc2['bin']
369
401
  assert_kind_of Binary, bin2
370
402
  assert_equal [1, 2, 3, 4, 5], bin2.to_a
371
- assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
403
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
372
404
  end
373
405
 
374
406
  def test_put_id_first
@@ -393,15 +425,24 @@ class BSONTest < Test::Unit::TestCase
393
425
  if !(RUBY_PLATFORM =~ /java/)
394
426
  def test_timestamp
395
427
  val = {"test" => [4, 20]}
396
- assert_equal val, @encoder.deserialize([0x13, 0x00, 0x00, 0x00,
397
- 0x11, 0x74, 0x65, 0x73,
398
- 0x74, 0x00, 0x04, 0x00,
399
- 0x00, 0x00, 0x14, 0x00,
400
- 0x00, 0x00, 0x00])
428
+ result = @encoder.deserialize([0x13, 0x00, 0x00, 0x00,
429
+ 0x11, 0x74, 0x65, 0x73,
430
+ 0x74, 0x00, 0x04, 0x00,
431
+ 0x00, 0x00, 0x14, 0x00,
432
+ 0x00, 0x00, 0x00])
401
433
 
434
+ assert_equal 4, result["test"][0]
435
+ assert_equal 20, result["test"][1]
402
436
  end
403
437
  end
404
438
 
439
+ def test_timestamp_type
440
+ ts = Timestamp.new(5000, 100)
441
+ doc = {:ts => ts}
442
+ bson = @encoder.serialize(doc)
443
+ assert_equal ts, @encoder.deserialize(bson)["ts"]
444
+ end
445
+
405
446
  def test_overflow
406
447
  doc = {"x" => 2**75}
407
448
  assert_raise RangeError do
@@ -439,7 +480,7 @@ class BSONTest < Test::Unit::TestCase
439
480
  rescue => e
440
481
  ensure
441
482
  assert_equal InvalidDocument, e.class
442
- assert_match /Cannot serialize/, e.message
483
+ assert_match(/Cannot serialize/, e.message)
443
484
  end
444
485
  end
445
486
  end
@@ -573,7 +614,7 @@ class BSONTest < Test::Unit::TestCase
573
614
  @encoder.serialize({"he\0llo" => "world"}, true)
574
615
  end
575
616
 
576
- assert_raise_error BSON::InvalidKeyName, "$hello" do
617
+ assert_raise BSON::InvalidKeyName do
577
618
  @encoder.serialize({"$hello" => "world"}, true)
578
619
  end
579
620
 
@@ -581,7 +622,7 @@ class BSONTest < Test::Unit::TestCase
581
622
  @encoder.serialize({"hello" => {"$hello" => "world"}}, true)
582
623
  end
583
624
 
584
- assert_raise_error BSON::InvalidKeyName, ".hello" do
625
+ assert_raise BSON::InvalidKeyName do
585
626
  @encoder.serialize({".hello" => "world"}, true)
586
627
  end
587
628