mongo 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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