mongo 0.0.2 → 0.0.3

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.
@@ -57,6 +57,23 @@ If you have the source code, you can run the tests.
57
57
 
58
58
  The tests assume that the Mongo database is running on the default port.
59
59
 
60
+ The project mongo-qa (http://github.com/mongodb/mongo-qa) contains many more
61
+ Mongo driver tests that are language independent. To run thoses tests as part
62
+ of the "rake test" task, run
63
+
64
+ $ rake mongo_qa
65
+ $ rake test
66
+
67
+ The mongo_qa task uses the "git clone" command to make a copy of that project
68
+ in a directory named mongo-qa. If the directory already exists, then the
69
+ mongo_qa task uses "git pull" to updated the code that's there. The Ruby
70
+ driver tests will then use some of the data files from that project when it
71
+ runs BSON tests. You can delete this directory at any time if you don't want
72
+ to run those tests any more.
73
+
74
+ Additionally, the script bin/validate is used by the mongo-qa project's
75
+ validator script.
76
+
60
77
 
61
78
  = Documentation
62
79
 
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ require 'rake/gempackagetask'
7
7
  require 'rake/contrib/rubyforgepublisher'
8
8
 
9
9
  GEM = "mongo"
10
- GEM_VERSION = '0.0.2'
10
+ GEM_VERSION = '0.0.3'
11
11
  SUMMARY = 'Simple pure-Ruby driver for the 10gen Mongo DB'
12
12
  DESCRIPTION = 'This is a simple pure-Ruby driver for the 10gen Mongo DB. For more information about Mongo, see http://www.mongodb.org.'
13
13
  AUTHOR = 'Jim Menard'
@@ -42,6 +42,17 @@ Rake::TestTask.new do |t|
42
42
  t.test_files = FileList['tests/test*.rb']
43
43
  end
44
44
 
45
+ desc "Clone or pull (update) the mongo-qa project used for testing"
46
+ task :mongo_qa do
47
+ if File.exist?('mongo-qa')
48
+ Dir.chdir('mongo-qa') do
49
+ system('git pull')
50
+ end
51
+ else
52
+ system('git clone git://github.com/mongodb/mongo-qa.git')
53
+ end
54
+ end
55
+
45
56
  desc "Generate documentation"
46
57
  task :rdoc do
47
58
  FileUtils.rm_rf('html')
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # usage: validate somefile.xson somefile.bson
4
+ #
5
+ # Reads somefile.xson file (XML that describes a Mongo-type document),
6
+ # converts it into a Ruby OrderedHash, runs that through the BSON
7
+ # serialization code, and writes the BSON bytes to somefile.bson.
8
+ #
9
+ # In addition, this script takes the generated BSON, reads it in then writes
10
+ # it back out to a temp BSON file. If they are different, we report that error
11
+ # to STDOUT.
12
+ #
13
+ # This script is used by the mongo-qa project
14
+ # (http://github.com/mongodb/mongo-qa).
15
+
16
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
17
+ require 'mongo'
18
+ require 'mongo/util/xml_to_ruby'
19
+
20
+ if ARGV.length < 2
21
+ $stderr.puts "usage: validate somefile.xson somefile.bson"
22
+ exit 1
23
+ end
24
+
25
+ # Translate the .xson XML into a Ruby object, turn that object into BSON, and
26
+ # write the BSON to the file as requested.
27
+ obj = File.open(ARGV[0], 'rb') { |f| XMLToRuby.new.xml_to_ruby(f) }
28
+ bson = BSON.new.serialize(obj).to_a
29
+ File.open(ARGV[1], 'wb') { |f| bson.each { |b| f.putc(b) } }
30
+
31
+ # Now the additional testing. Read the generated BSON back in, deserialize it,
32
+ # and re-serialize the results. Compare that BSON with the BSON from the file
33
+ # we output.
34
+ bson = File.open(ARGV[1], 'rb') { |f| f.read }
35
+ bson = if RUBY_VERSION >= '1.9'
36
+ bson.bytes.to_a
37
+ else
38
+ bson.split(//).collect { |c| c[0] }
39
+ end
40
+
41
+ # Turn the Ruby object into BSON bytes and compare with the BSON bytes from
42
+ # the file.
43
+ bson_from_ruby = BSON.new.serialize(obj).to_a
44
+
45
+ if bson.length != bson_from_ruby.length
46
+ $stderr.puts "error: round-trip BSON lengths differ when testing #{ARGV[0]}"
47
+ exit 1
48
+ elsif bson != bson_from_ruby
49
+ $stderr.puts "error: round-trip BSON contents differ when testing #{ARGV[0]}"
50
+ exit 1
51
+ end
@@ -1,6 +1,8 @@
1
1
  require 'mongo/mongo'
2
2
  require 'mongo/objectid'
3
3
  require 'mongo/dbref'
4
+ require 'mongo/binary'
5
+ require 'mongo/undefined'
4
6
  require 'mongo/message'
5
7
  require 'mongo/db'
6
8
  require 'mongo/cursor'
@@ -0,0 +1,34 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ module XGen
18
+ module Mongo
19
+ module Driver
20
+
21
+ # An array of binary bytes. The only reason this exists is so that the
22
+ # BSON encoder will know to output the Mongo BINARY type.
23
+ class Binary < String; end
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ class String
30
+ # Convert a string into a XGen::Mongo::Driver::Binary
31
+ def to_mongo_binary
32
+ XGen::Mongo::Driver::Binary.new(self)
33
+ end
34
+ end
@@ -167,7 +167,7 @@ module XGen
167
167
  buf.put_array(@db.socket.recv(size-4).unpack("C*"), 4)
168
168
  @n_remaining -= 1
169
169
  buf.rewind
170
- BSON.new.deserialize(buf)
170
+ BSON.new(@db).deserialize(buf)
171
171
  end
172
172
 
173
173
  def to_s
@@ -47,6 +47,14 @@ module XGen
47
47
  MACHINE = ( val = rand(0x1000000); [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff] )
48
48
  PID = ( val = rand(0x10000); [val & 0xff, (val >> 8) & 0xff]; )
49
49
 
50
+ # The string representation of an OID is different than its internal
51
+ # and BSON byte representations. The BYTE_ORDER here maps
52
+ # internal/BSON byte position (the index in BYTE_ORDER) to the
53
+ # position of the two hex characters representing that byte in the
54
+ # string representation. For example, the 0th BSON byte corresponds to
55
+ # the (0-based) 7th pair of hex chars in the string.
56
+ BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8]
57
+
50
58
  LOCK = Object.new
51
59
  LOCK.extend Mutex_m
52
60
 
@@ -57,12 +65,10 @@ module XGen
57
65
  # with that value.
58
66
  def self.from_string(str)
59
67
  data = []
60
- i = str.to_i(16)
61
- while i > 0
62
- data << (i & 0xff)
63
- i >>= 8
64
- end
65
- self.new(data.reverse)
68
+ BYTE_ORDER.each_with_index { |string_position, data_index|
69
+ data[data_index] = str[string_position * 2, 2].to_i(16)
70
+ }
71
+ self.new(data)
66
72
  end
67
73
 
68
74
  # +data+ is an array of bytes. If nil, a new id will be generated.
@@ -81,7 +87,11 @@ module XGen
81
87
  end
82
88
 
83
89
  def to_s
84
- @data.collect { |b| '%02x' % b }.join
90
+ str = ' ' * 24
91
+ BYTE_ORDER.each_with_index { |string_position, data_index|
92
+ str[string_position * 2, 2] = '%02x' % @data[data_index]
93
+ }
94
+ str
85
95
  end
86
96
 
87
97
  # (Would normally be private, but isn't so we can test it.)
@@ -0,0 +1,31 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ module XGen
18
+ module Mongo
19
+ module Driver
20
+
21
+ # A special "undefined" type to match Mongo's storage of UNKNOWN values.
22
+ # "UNKNOWN" comes from JavaScript.
23
+ #
24
+ # NOTE: this class does not attempt to provide ANY of the semantics an
25
+ # "unknown" object might need. It isn't nil, it isn't special in any
26
+ # way, and there isn't any singleton value.
27
+ class Undefined < Object; end
28
+
29
+ end
30
+ end
31
+ end
@@ -14,6 +14,7 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
  # ++
16
16
 
17
+ require 'base64'
17
18
  require 'mongo/util/byte_buffer'
18
19
  require 'mongo/util/ordered_hash'
19
20
  require 'mongo/objectid'
@@ -46,7 +47,9 @@ class BSON
46
47
  buf.put_array(val.to_s.unpack("C*") + [0])
47
48
  end
48
49
 
49
- def initialize
50
+ def initialize(db=nil)
51
+ # db is only needed during deserialization when the data contains a DBRef
52
+ @db = db
50
53
  @buf = ByteBuffer.new
51
54
  end
52
55
 
@@ -84,7 +87,13 @@ class BSON
84
87
  serialize_null_element(@buf, k)
85
88
  when REF
86
89
  serialize_dbref_element(@buf, k, v)
87
- when BINARY, UNDEFINED, SYMBOL, CODE_W_SCOPE
90
+ when SYMBOL
91
+ serialize_symbol_element(@buf, k, v)
92
+ when BINARY
93
+ serialize_binary_element(@buf, k, v)
94
+ when UNDEFINED
95
+ serialize_undefined_element(@buf, k)
96
+ when CODE_W_SCOPE
88
97
  # TODO
89
98
  raise "unimplemented type #{type}"
90
99
  else
@@ -96,13 +105,13 @@ class BSON
96
105
  self
97
106
  end
98
107
 
99
- def deserialize(buf=nil)
108
+ def deserialize(buf=nil, parent=nil)
100
109
  # If buf is nil, use @buf, assumed to contain already-serialized BSON.
101
110
  # This is only true during testing.
102
111
  @buf = ByteBuffer.new(buf.to_a) if buf
103
112
  @buf.rewind
104
113
  @buf.get_int # eat message size
105
- doc = {}
114
+ doc = OrderedHash.new
106
115
  while @buf.more?
107
116
  type = @buf.get
108
117
  case type
@@ -120,13 +129,13 @@ class BSON
120
129
  doc[key] = deserialize_oid_data(@buf)
121
130
  when ARRAY
122
131
  key = deserialize_cstr(@buf)
123
- doc[key] = deserialize_array_data(@buf)
132
+ doc[key] = deserialize_array_data(@buf, doc)
124
133
  when REGEX
125
134
  key = deserialize_cstr(@buf)
126
135
  doc[key] = deserialize_regex_data(@buf)
127
136
  when OBJECT
128
137
  key = deserialize_cstr(@buf)
129
- doc[key] = deserialize_object_data(@buf)
138
+ doc[key] = deserialize_object_data(@buf, doc)
130
139
  when BOOLEAN
131
140
  key = deserialize_cstr(@buf)
132
141
  doc[key] = deserialize_boolean_data(@buf)
@@ -136,10 +145,19 @@ class BSON
136
145
  when NULL
137
146
  key = deserialize_cstr(@buf)
138
147
  doc[key] = nil
148
+ when UNDEFINED
149
+ key = deserialize_cstr(@buf)
150
+ doc[key] = XGen::Mongo::Driver::Undefined.new
139
151
  when REF
140
152
  key = deserialize_cstr(@buf)
141
- doc[key] = deserialize_dbref_data(@buf)
142
- when BINARY, UNDEFINED, SYMBOL, CODE_W_SCOPE
153
+ doc[key] = deserialize_dbref_data(@buf, key, parent)
154
+ when SYMBOL
155
+ key = deserialize_cstr(@buf)
156
+ doc[key] = deserialize_symbol_data(@buf)
157
+ when BINARY
158
+ key = deserialize_cstr(@buf)
159
+ doc[key] = deserialize_binary_data(@buf)
160
+ when CODE_W_SCOPE
143
161
  # TODO
144
162
  raise "unimplemented type #{type}"
145
163
  when EOO
@@ -183,14 +201,14 @@ class BSON
183
201
  buf.get_int
184
202
  end
185
203
 
186
- def deserialize_object_data(buf)
204
+ def deserialize_object_data(buf, parent)
187
205
  size = buf.get_int
188
206
  buf.position -= 4
189
- BSON.new.deserialize(buf.get(size))
207
+ BSON.new(@db).deserialize(buf.get(size), parent)
190
208
  end
191
209
 
192
- def deserialize_array_data(buf)
193
- h = deserialize_object_data(buf)
210
+ def deserialize_array_data(buf, parent)
211
+ h = deserialize_object_data(buf, parent)
194
212
  a = []
195
213
  h.each { |k, v| a[k.to_i] = v }
196
214
  a
@@ -216,12 +234,22 @@ class BSON
216
234
  XGen::Mongo::Driver::ObjectID.new(buf.get(12))
217
235
  end
218
236
 
219
- def deserialize_dbref_data(buf)
237
+ def deserialize_dbref_data(buf, key, parent)
220
238
  ns = deserialize_cstr(buf)
221
239
  oid = deserialize_oid_data(buf)
222
- # TODO fix parent, field_name, db of DBRef. Does that need to be done here
223
- # or by the caller?
224
- XGen::Mongo::Driver::DBRef.new(nil, nil, nil, ns, oid)
240
+ XGen::Mongo::Driver::DBRef.new(parent, key, @db, ns, oid)
241
+ end
242
+
243
+ def deserialize_symbol_data(buf)
244
+ deserialize_cstr(buf).intern
245
+ end
246
+
247
+ def deserialize_binary_data(buf)
248
+ len = buf.get_int
249
+ bytes = buf.get(len)
250
+ str = ''
251
+ bytes.each { |c| str << c.chr }
252
+ str.to_mongo_binary
225
253
  end
226
254
 
227
255
  def serialize_eoo_element(buf)
@@ -240,6 +268,31 @@ class BSON
240
268
  buf.put_array(val.object_id.to_a)
241
269
  end
242
270
 
271
+ def serialize_symbol_element(buf, key, val)
272
+ buf.put(SYMBOL)
273
+ self.class.serialize_cstr(buf, key)
274
+ self.class.serialize_cstr(buf, val)
275
+ end
276
+
277
+ def serialize_binary_element(buf, key, val)
278
+ buf.put(BINARY)
279
+ self.class.serialize_cstr(buf, key)
280
+ buf.put_int(val.length)
281
+ bytes = if RUBY_VERSION >= '1.9'
282
+ val.bytes.to_a
283
+ else
284
+ a = []
285
+ val.each_byte { |byte| a << byte }
286
+ a
287
+ end
288
+ buf.put_array(bytes)
289
+ end
290
+
291
+ def serialize_undefined_element(buf, key)
292
+ buf.put(UNDEFINED)
293
+ self.class.serialize_cstr(buf, key)
294
+ end
295
+
243
296
  def serialize_boolean_element(buf, key, val)
244
297
  buf.put(BOOLEAN)
245
298
  self.class.serialize_cstr(buf, key)
@@ -337,6 +390,8 @@ class BSON
337
390
  NUMBER_INT
338
391
  when Numeric
339
392
  NUMBER
393
+ when XGen::Mongo::Driver::Binary # must be before String
394
+ BINARY
340
395
  when String
341
396
  # magic awful stuff - the DB requires that a where clause is sent as CODE
342
397
  key == "$where" ? CODE : STRING
@@ -354,6 +409,10 @@ class BSON
354
409
  DATE
355
410
  when Hash
356
411
  OBJECT
412
+ when Symbol
413
+ SYMBOL
414
+ when XGen::Mongo::Driver::Undefined
415
+ UNDEFINED
357
416
  else
358
417
  raise "Unknown type of object: #{o.class.name}"
359
418
  end
@@ -53,7 +53,7 @@ class OrderedHash < Hash
53
53
 
54
54
  def inspect
55
55
  str = '{'
56
- str << @ordered_keys.collect { |k| "\"#{k}\"=>#{self.[](k).inspect}" }.join(", ")
56
+ str << (@ordered_keys || []).collect { |k| "\"#{k}\"=>#{self.[](k).inspect}" }.join(", ")
57
57
  str << '}'
58
58
  end
59
59
 
@@ -0,0 +1,102 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ require 'rexml/document'
18
+ require 'mongo'
19
+
20
+ # Converts a .xson file (an XML file that describes a Mongo-type document) to
21
+ # an OrderedHash.
22
+ class XMLToRuby
23
+
24
+ include XGen::Mongo::Driver
25
+
26
+ def xml_to_ruby(io)
27
+ doc = REXML::Document.new(io)
28
+ doc_to_ruby(doc.root.elements['doc'])
29
+ end
30
+
31
+ protected
32
+
33
+ def element_to_ruby(e)
34
+ type = e.name
35
+ child = e.elements[1]
36
+ case type
37
+ when 'oid'
38
+ ObjectID.from_string(e.text)
39
+ when 'ref'
40
+ dbref_to_ruby(e.elements)
41
+ when 'int'
42
+ e.text.to_i
43
+ when 'number'
44
+ e.text.to_f
45
+ when 'string', 'code'
46
+ e.text.to_s
47
+ when 'binary'
48
+ Base64.decode64(e.text.to_s).to_mongo_binary
49
+ when 'symbol'
50
+ e.text.to_s.intern
51
+ when 'boolean'
52
+ e.text.to_s == 'true'
53
+ when 'array'
54
+ array_to_ruby(e.elements)
55
+ when 'date'
56
+ Time.at(e.text.to_f / 1000.0)
57
+ when 'regex'
58
+ regex_to_ruby(e.elements)
59
+ when 'null'
60
+ nil
61
+ when 'undefined'
62
+ Undefined.new
63
+ when 'doc'
64
+ doc_to_ruby(e)
65
+ else
66
+ raise "Unknown type #{type} in element with name #{e.attributes['name']}"
67
+ end
68
+ end
69
+
70
+ def doc_to_ruby(element)
71
+ oh = OrderedHash.new
72
+ element.elements.each { |e| oh[e.attributes['name']] = element_to_ruby(e) }
73
+ oh
74
+ end
75
+
76
+ def array_to_ruby(elements)
77
+ a = []
78
+ elements.each { |e|
79
+ index_str = e.attributes['name']
80
+ a[index_str.to_i] = element_to_ruby(e)
81
+ }
82
+ a
83
+ end
84
+
85
+ def regex_to_ruby(elements)
86
+ pattern = elements['pattern'].text
87
+ options_str = elements['options'].text || ''
88
+
89
+ options = 0
90
+ options |= Regexp::IGNORECASE if options_str.include?('i')
91
+ options |= Regexp::MULTILINE if options_str.include?('m')
92
+ options |= Regexp::EXTENDED if options_str.include?('x')
93
+ Regexp.new(pattern, options)
94
+ end
95
+
96
+ def dbref_to_ruby(elements)
97
+ ns = elements['ns'].text
98
+ oid_str = elements['oid'].text
99
+ DBRef.new(nil, nil, nil, ns, ObjectID.from_string(oid_str))
100
+ end
101
+
102
+ end
@@ -8,6 +8,9 @@ class BSONTest < Test::Unit::TestCase
8
8
  include XGen::Mongo::Driver
9
9
 
10
10
  def setup
11
+ # We don't pass a DB to the constructor, even though we are about to test
12
+ # deserialization. This means that when we deserialize, any DBRefs will
13
+ # have nil @db ivars. That's fine for now.
11
14
  @b = BSON.new
12
15
  end
13
16
 
@@ -79,11 +82,36 @@ class BSONTest < Test::Unit::TestCase
79
82
 
80
83
  def test_dbref
81
84
  oid = ObjectID.new
82
- doc = {'dbref' => DBRef.new(nil, nil, nil, 'namespace', oid)}
85
+ doc = {}
86
+ doc['dbref'] = DBRef.new(doc, 'dbref', nil, 'namespace', oid)
83
87
  @b.serialize(doc)
84
88
  doc2 = @b.deserialize
85
89
  assert_equal 'namespace', doc2['dbref'].namespace
86
90
  assert_equal oid, doc2['dbref'].object_id
87
91
  end
88
92
 
93
+ def test_symbol
94
+ doc = {'sym' => :foo}
95
+ @b.serialize(doc)
96
+ doc2 = @b.deserialize
97
+ assert_equal :foo, doc2['sym']
98
+ end
99
+
100
+ def test_binary
101
+ bin = 'binstring'.to_mongo_binary
102
+ assert_kind_of Binary, bin
103
+
104
+ doc = {'bin' => bin}
105
+ @b.serialize(doc)
106
+ doc2 = @b.deserialize
107
+ assert_equal 'binstring', doc2['bin']
108
+ end
109
+
110
+ def test_undefined
111
+ doc = {'undef' => Undefined.new}
112
+ @b.serialize(doc)
113
+ doc2 = @b.deserialize
114
+ assert_kind_of Undefined, doc2['undef']
115
+ end
116
+
89
117
  end
@@ -73,4 +73,16 @@ class ObjectIDTest < Test::Unit::TestCase
73
73
  assert_equal @o.to_s, o2.to_s
74
74
  end
75
75
 
76
+ def test_from_string_leading_zeroes
77
+ hex_str = '000000000000000000abcdef'
78
+ o = ObjectID.from_string(hex_str)
79
+ assert_equal hex_str, o.to_s
80
+ end
81
+
82
+ def test_byte_order
83
+ hex_str = '000102030405060708090A0B'
84
+ o = ObjectID.from_string(hex_str)
85
+ assert_equal [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0b, 0x0a, 0x09, 0x08], o.to_a
86
+ end
87
+
76
88
  end
@@ -1,11 +1,16 @@
1
1
  HERE = File.dirname(__FILE__)
2
2
  $LOAD_PATH[0,0] = File.join(HERE, '..', 'lib')
3
3
  require 'mongo'
4
- require 'rexml/document'
4
+ require 'mongo/util/xml_to_ruby'
5
5
  require 'test/unit'
6
6
 
7
7
  # For each xml/bson file in the data subdirectory, we turn the XML into an
8
8
  # OrderedHash and then test both Ruby-to-BSON and BSON-to-Ruby translations.
9
+ #
10
+ # There is a whole other project that includes similar tests
11
+ # (http://github.com/mongodb/mongo-qa). If the directory ../mongo-qa exists,
12
+ # then we find the BSON test files there and use those, too. Use the Rake task
13
+ # "mongo_qa" to obtain those tests.
9
14
  class RoundTripTest < Test::Unit::TestCase
10
15
 
11
16
  include XGen::Mongo::Driver
@@ -16,117 +21,96 @@ class RoundTripTest < Test::Unit::TestCase
16
21
  unless @@ruby
17
22
  names = Dir[File.join(HERE, 'data', '*.xml')].collect {|f| File.basename(f).sub(/\.xml$/, '') }
18
23
  @@ruby = {}
19
- names.each { |name| @@ruby[name] = xml_to_ruby(name) }
24
+ names.each { |name|
25
+ File.open(File.join(HERE, 'data', "#{name}.xml")) { |f|
26
+ @@ruby[name] = XMLToRuby.new.xml_to_ruby(f)
27
+ }
28
+ }
20
29
  end
21
30
  end
22
31
 
23
- def xml_to_ruby(name)
24
- File.open(File.join(HERE, 'data', "#{name}.xml")) { |f|
25
- doc = REXML::Document.new(f)
26
- doc_to_ruby(doc.root.elements['doc'])
27
- }
28
- end
29
-
30
- def element_to_ruby(e)
31
- type = e.name
32
- child = e.elements[1]
33
- case type
34
- when 'oid'
35
- ObjectID.from_string(e.text)
36
- when 'ref'
37
- dbref_to_ruby(e.elements)
38
- when 'int'
39
- e.text.to_i
40
- when 'number'
41
- e.text.to_f
42
- when 'string', 'code'
43
- e.text.to_s
44
- when 'boolean'
45
- e.text.to_s == 'true'
46
- when 'array'
47
- array_to_ruby(e.elements)
48
- when 'date'
49
- Time.at(e.text.to_f / 1000.0)
50
- when 'regex'
51
- regex_to_ruby(e.elements)
52
- when 'null'
53
- nil
54
- when 'doc'
55
- doc_to_ruby(e)
56
- else
57
- raise "Unknown type #{type} in element with name #{e.attributes['name']}"
58
- end
32
+ def test_dummy
33
+ assert true
59
34
  end
60
35
 
61
- def doc_to_ruby(element)
62
- oh = OrderedHash.new
63
- element.elements.each { |e| oh[e.attributes['name']] = element_to_ruby(e) }
64
- oh
36
+ def self.create_test_for_round_trip_files_in_dir(dir)
37
+ names = Dir[File.join(dir, '*.xson')].collect {|f| File.basename(f).sub(/\.xson$/, '') }
38
+ names.each { |name|
39
+ eval <<EOS
40
+ def test_#{name}_#{dir.gsub(/[^a-zA-Z0-9_]/, '_')}
41
+ one_round_trip("#{dir}", "#{name}")
42
+ end
43
+ EOS
44
+ }
65
45
  end
66
46
 
67
- def array_to_ruby(elements)
68
- a = []
69
- elements.each { |e|
70
- index_str = e.attributes['name']
71
- a[index_str.to_i] = element_to_ruby(e)
47
+ # Dynamically generate one test for each test file. This way, if one test
48
+ # fails the others will still run.
49
+ create_test_for_round_trip_files_in_dir(File.join(HERE, 'data'))
50
+ mongo_qa_dir = File.join(HERE, '..', 'mongo-qa/modules/bson_tests')
51
+ if File.exist?(mongo_qa_dir)
52
+ %w(basic_types complex single_types).each { |subdir_name|
53
+ create_test_for_round_trip_files_in_dir(File.join(mongo_qa_dir, subdir_name))
72
54
  }
73
- a
74
55
  end
75
56
 
76
- def regex_to_ruby(elements)
77
- pattern = elements['pattern'].text
78
- options_str = elements['options'].text || ''
79
-
80
- options = 0
81
- options |= Regexp::IGNORECASE if options_str.include?('i')
82
- options |= Regexp::MULTILINE if options_str.include?('m')
83
- options |= Regexp::EXTENDED if options_str.include?('x')
84
- Regexp.new(pattern, options)
85
- end
57
+ # Round-trip comparisons of Ruby-to-BSON and back.
58
+ # * Take the objects that were read from XML
59
+ # * Turn them into BSON bytes
60
+ # * Compare that with the BSON files we have
61
+ # * Turn those BSON bytes back in to Ruby objects
62
+ # * Turn them back into BSON bytes
63
+ # * Compare that with the BSON files we have (or the bytes that were already
64
+ # generated)
65
+ def one_round_trip(dir, name)
66
+ obj = File.open(File.join(dir, "#{name}.xson")) { |f|
67
+ XMLToRuby.new.xml_to_ruby(f)
68
+ }
86
69
 
87
- def dbref_to_ruby(elements)
88
- ns = elements['ns'].text
89
- oid_str = elements['oid'].text
90
- DBRef.new(nil, nil, nil, ns, ObjectID.from_string(oid_str))
91
- end
70
+ File.open(File.join(dir, "#{name}.bson"), 'r') { |f|
71
+ # Read the BSON from the file
72
+ bson = f.read
73
+ bson = if RUBY_VERSION >= '1.9'
74
+ bson.bytes.to_a
75
+ else
76
+ bson.split(//).collect { |c| c[0] }
77
+ end
92
78
 
93
- # DEBUG
94
- # def test_foo
95
- # $stderr.puts "#{@@ruby['smorgasbord'].class.name}" # DEBUG
96
- # $stderr.puts "@@ruby['smorgasbord'] = #{@@ruby['smorgasbord'].inspect.gsub(/, /, ",\n")}" # DEBUG
97
- # end
79
+ # Turn the Ruby object into BSON bytes and compare with the BSON bytes
80
+ # from the file.
81
+ bson_from_ruby = BSON.new.serialize(obj).to_a
98
82
 
99
- def test_ruby_to_bson
100
- @@ruby.each { |name, obj|
101
- File.open(File.join(HERE, 'data', "#{name}.bson"), 'r') { |f|
102
- bson = f.read
103
- bson = if RUBY_VERSION >= '1.9'
104
- bson.bytes
105
- else
106
- bson.split(//).collect { |c| c[0] }
107
- end
108
- bson_from_ruby = BSON.new.serialize(obj).to_a
83
+ begin
109
84
  assert_equal bson.length, bson_from_ruby.length
110
85
  assert_equal bson, bson_from_ruby
111
- }
86
+ rescue => ex
87
+ # File.open(File.join(dir, "#{name}_out_a.bson"), 'wb') { |f| # DEBUG
88
+ # bson_from_ruby.each { |b| f.putc(b) }
89
+ # }
90
+ raise ex
91
+ end
92
+
93
+ # Turn those BSON bytes back into a Ruby object.
94
+ #
95
+ # We're passing a nil db to the contructor here, but that's OK because
96
+ # the BSON DBFef bytes don't contain the db object in any case, and we
97
+ # don't care what the database is.
98
+ obj_from_bson = BSON.new(nil).deserialize(ByteBuffer.new(bson_from_ruby))
99
+ assert_kind_of OrderedHash, obj_from_bson
100
+
101
+ # Turn that Ruby object into BSON and compare it to the original BSON
102
+ # bytes.
103
+ bson_from_ruby = BSON.new.serialize(obj_from_bson).to_a
104
+ begin
105
+ assert_equal bson.length, bson_from_ruby.length
106
+ assert_equal bson, bson_from_ruby
107
+ rescue => ex
108
+ # File.open(File.join(dir, "#{name}_out_b.bson"), 'wb') { |f| # DEBUG
109
+ # bson_from_ruby.each { |b| f.putc(b) }
110
+ # }
111
+ raise ex
112
+ end
112
113
  }
113
114
  end
114
115
 
115
- # def test_bson_to_ruby
116
- # @@ruby.each { |name, obj|
117
- # File.open(File.join(HERE, 'data', "#{name}.bson"), 'r') { |f|
118
- # bson = f.read
119
- # obj_from_bson = BSON.new.deserialize(ByteBuffer.new(bson.split(//)))
120
- # assert_equal obj, obj_from_bson
121
- # }
122
- # }
123
- # end
124
-
125
- # # Technically reduntant, given the other tests
126
- # def test_roundtrips
127
- # @@ruby.each { |name, obj|
128
-
129
- # }
130
- # end
131
-
132
116
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Menard
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-09 00:00:00 -05:00
12
+ date: 2009-01-12 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -23,7 +23,9 @@ extra_rdoc_files: []
23
23
 
24
24
  files:
25
25
  - bin/mongo_console
26
+ - bin/validate
26
27
  - lib/mongo/admin.rb
28
+ - lib/mongo/binary.rb
27
29
  - lib/mongo/collection.rb
28
30
  - lib/mongo/cursor.rb
29
31
  - lib/mongo/db.rb
@@ -42,9 +44,11 @@ files:
42
44
  - lib/mongo/mongo.rb
43
45
  - lib/mongo/objectid.rb
44
46
  - lib/mongo/query.rb
47
+ - lib/mongo/undefined.rb
45
48
  - lib/mongo/util/bson.rb
46
49
  - lib/mongo/util/byte_buffer.rb
47
50
  - lib/mongo/util/ordered_hash.rb
51
+ - lib/mongo/util/xml_to_ruby.rb
48
52
  - lib/mongo.rb
49
53
  - tests/test_admin.rb
50
54
  - tests/test_bson.rb