re-require shoulda; gridfs decoupling

This commit is contained in:
Kyle Banker 2010-02-22 15:49:04 -05:00
parent 54a68c7438
commit 89fe06250e
14 changed files with 635 additions and 629 deletions

View File

@ -292,10 +292,11 @@ It's also possible to test replica pairs with connection pooling:
$ rake test:pooled_pair_insert $ rake test:pooled_pair_insert
===Mocha ===Shoulda and Mocha
Running the test suite requires mocha. You can install it as follows: Running the test suite requires shoulda and mocha. You can install them as follows:
$ gem install shoulda
$ gem install mocha $ gem install mocha
The tests assume that the Mongo database is running on the default port. You The tests assume that the Mongo database is running on the default port. You

View File

@ -22,22 +22,25 @@ module Mongo
def initialize(db, fs_name=DEFAULT_FS_NAME) def initialize(db, fs_name=DEFAULT_FS_NAME)
check_params(db) check_params(db)
@db = db @db = db
@files = @db["#{fs_name}.files"] @files = @db["#{fs_name}.files"]
@chunks = @db["#{fs_name}.chunks"] @chunks = @db["#{fs_name}.chunks"]
@fs_name = fs_name
@chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]]) @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
end end
def put(data, filename, opts={}) def put(data, filename, opts={})
file = GridIO.new(@files, @chunks, filename, 'w', false, opts=opts) opts.merge!(default_grid_io_opts)
file = GridIO.new(@files, @chunks, filename, 'w', opts=opts)
file.write(data) file.write(data)
file.close file.close
file.files_id file.files_id
end end
def get(id) def get(id)
GridIO.new(@files, @chunks, nil, 'r', false, :_id => id) opts = {:query => {'_id' => id}}.merge!(default_grid_io_opts)
GridIO.new(@files, @chunks, nil, 'r', opts)
end end
def delete(id) def delete(id)
@ -47,6 +50,10 @@ module Mongo
private private
def default_grid_io_opts
{:fs_name => @fs_name}
end
def check_params(db) def check_params(db)
if !db.is_a?(Mongo::DB) if !db.is_a?(Mongo::DB)
raise MongoArgumentError, "db must be an instance of Mongo::DB." raise MongoArgumentError, "db must be an instance of Mongo::DB."

View File

@ -23,10 +23,12 @@ module Mongo
super super
@files.create_index([['filename', 1], ['uploadDate', -1]]) @files.create_index([['filename', 1], ['uploadDate', -1]])
@default_query_opts = {:sort => [['filename', 1], ['uploadDate', -1]], :limit => 1}
end end
def open(filename, mode, opts={}) def open(filename, mode, opts={})
file = GridIO.new(@files, @chunks, filename, mode, true, opts) opts.merge!(default_grid_io_opts(filename))
file = GridIO.new(@files, @chunks, filename, mode, opts)
return file unless block_given? return file unless block_given?
result = nil result = nil
begin begin
@ -37,15 +39,31 @@ module Mongo
result result
end end
def put(data, filename) def put(data, filename, opts={})
opts.merge!(default_grid_io_opts(filename))
file = GridIO.new(@files, @chunks, filename, 'w', opts)
file.write(data)
file.close
file.files_id
end end
def get(id) def get(filename, opts={})
opts.merge!(default_grid_io_opts(filename))
GridIO.new(@files, @chunks, filename, 'r', opts)
end end
# Deletes all files matching the given criteria. def delete(filename, opts={})
def delete(criteria) ids = @files.find({'filename' => filename}, ['_id'])
ids.each do |id|
@files.remove({'_id' => id})
@chunks.remove('files_id' => id)
end
end end
private
def default_grid_io_opts(filename=nil)
{:fs_name => @fs_name, :query => {'filename' => filename}, :query_opts => @default_query_opts}
end
end end
end end

View File

@ -23,20 +23,20 @@ module Mongo
attr_reader :content_type, :chunk_size, :upload_date, :files_id, :filename, :metadata attr_reader :content_type, :chunk_size, :upload_date, :files_id, :filename, :metadata
def initialize(files, chunks, filename, mode, filesystem, opts={}) def initialize(files, chunks, filename, mode, opts={})
@files = files @files = files
@chunks = chunks @chunks = chunks
@filename = filename @filename = filename
@mode = mode @mode = mode
@content_type = opts[:content_type] || DEFAULT_CONTENT_TYPE @query = opts[:query] || {}
@chunk_size = opts[:chunk_size] || DEFAULT_CHUNK_SIZE @query_opts = opts[:query_opts] || {}
@files_id = opts[:_id] @fs_name = opts[:fs_name] || Grid::DEFAULT_FS_NAME
case @mode case @mode
when 'r' then init_read(filesystem, opts) when 'r' then init_read(opts)
when 'w' then init_write(opts) when 'w' then init_write(opts)
else else
raise GridError, "Invalid file mode #{@mode}. Valid options include 'r' and 'w'." raise GridError, "Invalid file mode #{@mode}. Mode should be 'r' or 'w'."
end end
end end
@ -147,9 +147,7 @@ module Mongo
chunk chunk
end end
# TODO: Perhaps use an upsert here instead?
def save_chunk(chunk) def save_chunk(chunk)
@chunks.remove('_id' => chunk['_id'])
@chunks.insert(chunk) @chunks.insert(chunk)
end end
@ -159,22 +157,17 @@ module Mongo
chunk chunk
end end
def get_chunk_for_read(n)
chunk = get_chunk(n)
return nil unless chunk
end
def last_chunk_number def last_chunk_number
(@file_length / @chunk_size).to_i (@file_length / @chunk_size).to_i
end end
# Read a file in its entirety (optimized). # Read a file in its entirety.
def read_all def read_all
buf = '' buf = ''
while true while true
buf << @current_chunk['data'].to_s buf << @current_chunk['data'].to_s
break if @current_chunk['n'] == last_chunk_number
@current_chunk = get_chunk(@current_chunk['n'] + 1) @current_chunk = get_chunk(@current_chunk['n'] + 1)
break unless @current_chunk
end end
buf buf
end end
@ -232,15 +225,10 @@ module Mongo
string.length - to_write string.length - to_write
end end
# Initialize based on whether the supplied file exists. # Initialize the class for reading a file.
def init_read(filesystem, opts) def init_read(opts)
if filesystem doc = @files.find(@query, @query_opts).next_document
doc = @files.find({'filename' => @filename}, :sort => [["uploadDate", -1]], :limit => 1).next_document raise GridError, "Could not open file matching #{@query.inspect} #{@query_opts.inspect}" unless doc
raise GridError, "Could not open file with filename #{@filename}" unless doc
else
doc = @files.find({'_id' => @files_id}).next_document
raise GridError, "Could not open file with id #{@files_id}" unless doc
end
@files_id = doc['_id'] @files_id = doc['_id']
@content_type = doc['contentType'] @content_type = doc['contentType']
@ -251,11 +239,12 @@ module Mongo
@metadata = doc['metadata'] @metadata = doc['metadata']
@md5 = doc['md5'] @md5 = doc['md5']
@filename = doc['filename'] @filename = doc['filename']
@current_chunk = get_chunk(0) @current_chunk = get_chunk(0)
@file_position = 0 @file_position = 0
end end
# Validates and sets up the class for the given file mode. # Initialize the class for writing a file.
def init_write(opts) def init_write(opts)
@files_id = opts[:_id] || Mongo::ObjectID.new @files_id = opts[:_id] || Mongo::ObjectID.new
@content_type = opts[:content_type] || @content_type || DEFAULT_CONTENT_TYPE @content_type = opts[:content_type] || @content_type || DEFAULT_CONTENT_TYPE
@ -281,7 +270,7 @@ module Mongo
# Get a server-side md5. # Get a server-side md5.
md5_command = OrderedHash.new md5_command = OrderedHash.new
md5_command['filemd5'] = @files_id md5_command['filemd5'] = @files_id
md5_command['root'] = 'fs' md5_command['root'] = @fs_name
h['md5'] = @files.db.command(md5_command)['md5'] h['md5'] = @files.db.command(md5_command)['md5']
h h

View File

@ -1,13 +1,15 @@
# encoding:utf-8 # encoding:utf-8
require 'test/test_helper' require 'test/test_helper'
context "Inspecting" do class BinaryTest < Test::Unit::TestCase
setup do context "Inspecting" do
@data = ("THIS IS BINARY " * 50).unpack("c*") setup do
end @data = ("THIS IS BINARY " * 50).unpack("c*")
end
should "not display actual data" do should "not display actual data" do
binary = Mongo::Binary.new(@data) binary = Mongo::Binary.new(@data)
assert_equal "<Mongo::Binary:#{binary.object_id}>", binary.inspect assert_equal "<Mongo::Binary:#{binary.object_id}>", binary.inspect
end
end end
end end

View File

@ -1,7 +1,7 @@
require 'test/test_helper' require 'test/test_helper'
class TestCollection < Test::Unit::TestCase class TestCollection < Test::Unit::TestCase
@@connection = Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT) @@connection ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT)
@@db = @@connection.db('ruby-mongo-test') @@db = @@connection.db('ruby-mongo-test')
@@test = @@db.collection("test") @@test = @@db.collection("test")
@@version = @@connection.server_version @@version = @@connection.server_version
@ -75,32 +75,31 @@ class TestCollection < Test::Unit::TestCase
end end
if @@version > "1.1" if @@version > "1.1"
context "distinct queries" do def setup_for_distinct
setup do @@test.remove
@@test.remove @@test.insert([{:a => 0, :b => {:c => "a"}},
@@test.insert([{:a => 0, :b => {:c => "a"}}, {:a => 1, :b => {:c => "b"}},
{:a => 1, :b => {:c => "b"}}, {:a => 1, :b => {:c => "c"}},
{:a => 1, :b => {:c => "c"}}, {:a => 2, :b => {:c => "a"}},
{:a => 2, :b => {:c => "a"}}, {:a => 3},
{:a => 3}, {:a => 3}])
{:a => 3}]) end
def test_distinct_queries
setup_for_distinct
assert_equal [0, 1, 2, 3], @@test.distinct(:a).sort
assert_equal ["a", "b", "c"], @@test.distinct("b.c").sort
end
if @@version >= "1.2"
def test_filter_collection_with_query
setup_for_distinct
assert_equal [2, 3], @@test.distinct(:a, {:a => {"$gt" => 1}}).sort
end end
should "return distinct values" do def test_filter_nested_objects
assert_equal [0, 1, 2, 3], @@test.distinct(:a).sort setup_for_distinct
assert_equal ["a", "b", "c"], @@test.distinct("b.c").sort assert_equal ["a", "b"], @@test.distinct("b.c", {"b.c" => {"$ne" => "c"}}).sort
end
if @@version >= "1.2"
should "filter collection with query" do
assert_equal [2, 3], @@test.distinct(:a, {:a => {"$gt" => 1}}).sort
end
should "filter nested objects" do
assert_equal ["a", "b"], @@test.distinct("b.c", {"b.c" => {"$ne" => "c"}}).sort
end
end end
end end
end end

View File

@ -1,184 +1,186 @@
require 'test/test_helper' require 'test/test_helper'
include Mongo include Mongo
context "GridFileSystem:" do class GridFileSystemTest < Test::Unit::TestCase
setup do context "GridFileSystem:" do
@db ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT).db('ruby-mongo-test')
@files = @db.collection('fs.files')
@chunks = @db.collection('fs.chunks')
end
teardown do
@files.remove
@chunks.remove
end
context "When reading:" do
setup do setup do
@data = "CHUNKS" * 50000 @con = Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
@grid = GridFileSystem.new(@db) ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT)
@grid.open('sample', 'w') do |f| @db = @con.db('mongo-ruby-test')
f.write @data
end
@grid = GridFileSystem.new(@db)
end end
should "read sample data" do teardown do
data = @grid.open('sample', 'r') { |f| f.read } @db['fs.files'].remove
assert_equal data.length, @data.length @db['fs.chunks'].remove
end end
should "return an empty string if length is zero" do context "When reading:" do
data = @grid.open('sample', 'r') { |f| f.read(0) }
assert_equal '', data
end
should "return the first n bytes" do
data = @grid.open('sample', 'r') {|f| f.read(288888) }
assert_equal 288888, data.length
assert_equal @data[0...288888], data
end
should "return the first n bytes even with an offset" do
data = @grid.open('sample', 'r') do |f|
f.seek(1000)
f.read(288888)
end
assert_equal 288888, data.length
assert_equal @data[1000...289888], data
end
end
context "When writing:" do
setup do
@data = "BYTES" * 50000
@grid = GridFileSystem.new(@db)
@grid.open('sample', 'w') do |f|
f.write @data
end
end
should "read sample data" do
data = @grid.open('sample', 'r') { |f| f.read }
assert_equal data.length, @data.length
end
should "return the total number of bytes written" do
data = 'a' * 300000
assert_equal 300000, @grid.open('write', 'w') {|f| f.write(data) }
end
should "more read sample data" do
data = @grid.open('sample', 'r') { |f| f.read }
assert_equal data.length, @data.length
end
should "raise exception if not opened for write" do
assert_raise GridError do
@grid.open('io', 'r') { |f| f.write('hello') }
end
end
context "and when overwriting the file" do
setup do setup do
@old = @grid.open('sample', 'r') @chunks_data = "CHUNKS" * 50000
@grid = GridFileSystem.new(@db)
@new_data = "DATA" * 1000 @grid.open('sample.file', 'w') do |f|
@grid.open('sample', 'w') do |f| f.write @chunks_data
f.write @new_data
end end
@new = @grid.open('sample', 'r') @grid = GridFileSystem.new(@db)
end end
should "have a newer upload date" do should "read sample data" do
assert @new.upload_date > @old.upload_date data = @grid.open('sample.file', 'r') { |f| f.read }
assert_equal data.length, @chunks_data.length
end end
should "have a different files_id" do should "return an empty string if length is zero" do
assert_not_equal @new.files_id, @old.files_id data = @grid.open('sample.file', 'r') { |f| f.read(0) }
assert_equal '', data
end end
should "contain the new data" do should "return the first n bytes" do
assert_equal @new_data, @new.read data = @grid.open('sample.file', 'r') {|f| f.read(288888) }
assert_equal 288888, data.length
assert_equal @chunks_data[0...288888], data
end end
end
end
context "When writing chunks:" do should "return the first n bytes even with an offset" do
setup do data = @grid.open('sample.file', 'r') do |f|
data = "B" * 50000 f.seek(1000)
@grid = GridFileSystem.new(@db) f.read(288888)
@grid.open('sample', 'w', :chunk_size => 1000) do |f| end
f.write data assert_equal 288888, data.length
assert_equal @chunks_data[1000...289888], data
end end
end end
should "write the correct number of chunks" do context "When writing:" do
file = @files.find_one({:filename => 'sample'}) setup do
chunks = @chunks.find({'files_id' => file['_id']}).to_a @data = "BYTES" * 50
assert_equal 50, chunks.length @grid = GridFileSystem.new(@db)
end @grid.open('sample', 'w') do |f|
end f.write @data
end
end
context "Positioning:" do should "read sample data" do
setup do data = @grid.open('sample', 'r') { |f| f.read }
data = 'hello, world' + '1' * 5000 + 'goodbye!' + '2' * 1000 + '!' assert_equal data.length, @data.length
@grid = GridFileSystem.new(@db) end
@grid.open('hello', 'w', :chunk_size => 1000) do |f|
f.write data should "return the total number of bytes written" do
data = 'a' * 300000
assert_equal 300000, @grid.open('sample', 'w') {|f| f.write(data) }
end
should "more read sample data" do
data = @grid.open('sample', 'r') { |f| f.read }
assert_equal data.length, @data.length
end
should "raise exception if not opened for write" do
assert_raise GridError do
@grid.open('io', 'r') { |f| f.write('hello') }
end
end
context "and when overwriting the file" do
setup do
@old = @grid.open('sample', 'r')
@new_data = "DATA" * 10
sleep(2)
@grid.open('sample', 'w') do |f|
f.write @new_data
end
@new = @grid.open('sample', 'r')
end
should "have a newer upload date" do
assert @new.upload_date > @old.upload_date, "New data is not greater than old date."
end
should "have a different files_id" do
assert_not_equal @new.files_id, @old.files_id
end
should "contain the new data" do
assert_equal @new_data, @new.read, "Expected DATA"
end
end
end
context "When writing chunks:" do
setup do
data = "B" * 50000
@grid = GridFileSystem.new(@db)
@grid.open('sample', 'w', :chunk_size => 1000) do |f|
f.write data
end
end
should "write the correct number of chunks" do
file = @db['fs.files'].find_one({:filename => 'sample'})
chunks = @db['fs.chunks'].find({'files_id' => file['_id']}).to_a
assert_equal 50, chunks.length
end end
end end
should "seek within chunks" do context "Positioning:" do
@grid.open('hello', 'r') do |f| setup do
f.seek(0) data = 'hello, world' + '1' * 5000 + 'goodbye!' + '2' * 1000 + '!'
assert_equal 'h', f.read(1) @grid = GridFileSystem.new(@db)
f.seek(7) @grid.open('hello', 'w', :chunk_size => 1000) do |f|
assert_equal 'w', f.read(1) f.write data
f.seek(4) end
assert_equal 'o', f.read(1)
f.seek(0)
f.seek(7, IO::SEEK_CUR)
assert_equal 'w', f.read(1)
f.seek(-2, IO::SEEK_CUR)
assert_equal ' ', f.read(1)
f.seek(-4, IO::SEEK_CUR)
assert_equal 'l', f.read(1)
f.seek(3, IO::SEEK_CUR)
assert_equal 'w', f.read(1)
end end
end
should "seek between chunks" do should "seek within chunks" do
@grid.open('hello', 'r') do |f| @grid.open('hello', 'r') do |f|
f.seek(1000) f.seek(0)
assert_equal '11111', f.read(5) assert_equal 'h', f.read(1)
f.seek(7)
f.seek(5009) assert_equal 'w', f.read(1)
assert_equal '111goodbye!222', f.read(14) f.seek(4)
assert_equal 'o', f.read(1)
f.seek(-1, IO::SEEK_END) f.seek(0)
assert_equal '!', f.read(1) f.seek(7, IO::SEEK_CUR)
f.seek(-6, IO::SEEK_END) assert_equal 'w', f.read(1)
assert_equal '2', f.read(1) f.seek(-2, IO::SEEK_CUR)
assert_equal ' ', f.read(1)
f.seek(-4, IO::SEEK_CUR)
assert_equal 'l', f.read(1)
f.seek(3, IO::SEEK_CUR)
assert_equal 'w', f.read(1)
end
end end
end
should "tell the current position" do should "seek between chunks" do
@grid.open('hello', 'r') do |f| @grid.open('hello', 'r') do |f|
assert_equal 0, f.tell f.seek(1000)
assert_equal '11111', f.read(5)
f.seek(999) f.seek(5009)
assert_equal 999, f.tell assert_equal '111goodbye!222', f.read(14)
f.seek(-1, IO::SEEK_END)
assert_equal '!', f.read(1)
f.seek(-6, IO::SEEK_END)
assert_equal '2', f.read(1)
end
end end
end
should "seek only in read mode" do should "tell the current position" do
assert_raise GridError do @grid.open('hello', 'r') do |f|
@grid.open('hello', 'w') {|f| f.seek(0) } assert_equal 0, f.tell
f.seek(999)
assert_equal 999, f.tell
end
end
should "seek only in read mode" do
assert_raise GridError do
@grid.open('hello', 'w') {|f| f.seek(0) }
end
end end
end end
end end

View File

@ -1,33 +1,37 @@
require 'test/test_helper' require 'test/test_helper'
include Mongo include Mongo
context "" do class GridIOTest < Test::Unit::TestCase
setup do
@db ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT).db('ruby-mongo-test')
@files = @db.collection('fs.files')
@chunks = @db.collection('fs.chunks')
end
teardown do context "GridIO" do
@files.remove
@chunks.remove
end
context "Options" do
setup do setup do
@filename = 'test' @db ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
@mode = 'w' ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT).db('ruby-mongo-test')
@files = @db.collection('fs.files')
@chunks = @db.collection('fs.chunks')
end end
should "set default 256k chunk size" do teardown do
file = GridIO.new(@files, @chunks, @filename, @mode, false) @files.remove
assert_equal 256 * 1024, file.chunk_size @chunks.remove
end end
should "set chunk size" do context "Options" do
file = GridIO.new(@files, @chunks, @filename, @mode, false, :chunk_size => 1000) setup do
assert_equal 1000, file.chunk_size @filename = 'test'
@mode = 'w'
end
should "set default 256k chunk size" do
file = GridIO.new(@files, @chunks, @filename, @mode)
assert_equal 256 * 1024, file.chunk_size
end
should "set chunk size" do
file = GridIO.new(@files, @chunks, @filename, @mode, :chunk_size => 1000)
assert_equal 1000, file.chunk_size
end
end end
end end
end end

View File

@ -1,84 +1,87 @@
require 'test/test_helper' require 'test/test_helper'
include Mongo include Mongo
context "Tests:" do class GridTest < Test::Unit::TestCase
setup do context "Tests:" do
@db ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT).db('ruby-mongo-test')
@files = @db.collection('test-fs.files')
@chunks = @db.collection('test-fs.chunks')
end
teardown do
@files.remove
@chunks.remove
end
context "A basic grid-stored file" do
setup do setup do
@data = "GRIDDATA" * 50000 @db ||= Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
@grid = Grid.new(@db, 'test-fs') ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT).db('ruby-mongo-test')
@id = @grid.put(@data, 'sample', :metadata => {'app' => 'photos'}) @files = @db.collection('test-fs.files')
@chunks = @db.collection('test-fs.chunks')
end end
should "retrieve the stored data" do teardown do
data = @grid.get(@id).data @files.remove
assert_equal @data, data @chunks.remove
end end
should "store the filename" do context "A basic grid-stored file" do
file = @grid.get(@id) setup do
assert_equal 'sample', file.filename @data = "GRIDDATA" * 50000
end @grid = Grid.new(@db, 'test-fs')
@id = @grid.put(@data, 'sample', :metadata => {'app' => 'photos'})
should "store any relevant metadata" do
file = @grid.get(@id)
assert_equal 'photos', file.metadata['app']
end
should "delete the file and any chunks" do
@grid.delete(@id)
assert_raise GridError do
@grid.get(@id)
end
end
end
context "Streaming: " do
setup do
def read_and_write_stream(filename, read_length, opts={})
io = File.open(File.join(File.dirname(__FILE__), 'data', filename), 'r')
id = @grid.put(io, filename + read_length.to_s, opts)
file = @grid.get(id)
io.rewind
data = io.read
if data.respond_to?(:force_encoding)
data.force_encoding(:binary)
end
read_data = ""
while(chunk = file.read(read_length))
read_data << chunk
end
assert_equal data.length, read_data.length
end end
@grid = Grid.new(@db, 'test-fs') should "retrieve the stored data" do
data = @grid.get(@id).data
assert_equal @data, data
end
should "store the filename" do
file = @grid.get(@id)
assert_equal 'sample', file.filename
end
should "store any relevant metadata" do
file = @grid.get(@id)
assert_equal 'photos', file.metadata['app']
end
should "delete the file and any chunks" do
@grid.delete(@id)
assert_raise GridError do
@grid.get(@id)
end
end
end end
should "put and get a small io object with a small chunk size" do context "Streaming: " do || {}
read_and_write_stream('small_data.txt', 1, :chunk_size => 2) setup do
end def read_and_write_stream(filename, read_length, opts={})
io = File.open(File.join(File.dirname(__FILE__), 'data', filename), 'r')
id = @grid.put(io, filename + read_length.to_s, opts)
file = @grid.get(id)
io.rewind
data = io.read
if data.respond_to?(:force_encoding)
data.force_encoding(:binary)
end
read_data = ""
while(chunk = file.read(read_length))
read_data << chunk
end
assert_equal data.length, read_data.length
assert_equal data, read_data, "Unequal!"
end
should "put and get a small io object" do @grid = Grid.new(@db, 'test-fs')
read_and_write_stream('small_data.txt', 1) end
end
should "put and get a large io object when reading smaller than the chunk size" do should "put and get a small io object with a small chunk size" do
read_and_write_stream('sample_file.pdf', 256 * 1024) read_and_write_stream('small_data.txt', 1, :chunk_size => 2)
end end
should "put and get a large io object when reading larger than the chunk size" do should "put and get a small io object" do
read_and_write_stream('sample_file.pdf', 300 * 1024) read_and_write_stream('small_data.txt', 1)
end
should "put and get a large io object when reading smaller than the chunk size" do
read_and_write_stream('sample_file.pdf', 256 * 1024)
end
should "put and get a large io object when reading larger than the chunk size" do
read_and_write_stream('sample_file.pdf', 300 * 1024)
end
end end
end end
end end

View File

@ -5,12 +5,14 @@ require 'test/unit'
begin begin
require 'rubygems' require 'rubygems'
require 'shoulda'
require 'mocha' require 'mocha'
rescue LoadError rescue LoadError
puts <<MSG puts <<MSG
This test suite requires mocha. This test suite requires shoulda and mocha.
You can install it as follows: You can install them as follows:
gem install shoulda
gem install mocha gem install mocha
MSG MSG
@ -38,31 +40,3 @@ class Test::Unit::TestCase
end end
end end
end end
# shoulda-mini
# based on test/spec/mini 5
# http://gist.github.com/307649
# chris@ozmm.org
#
def context(*args, &block)
return super unless (name = args.first) && block
require 'test/unit'
klass = Class.new(Test::Unit::TestCase) do
def self.should(name, &block)
define_method("test_#{name.to_s.gsub(/\W/,'_')}", &block) if block
end
def self.xshould(*args) end
def self.context(*args, &block) instance_eval(&block) end
def self.setup(&block)
define_method(:setup) { self.class.setups.each { |s| instance_eval(&s) } }
setups << block
end
def self.setups; @setups ||= [] end
def self.teardown(&block) define_method(:teardown, &block) end
end
(class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
klass.class_eval do
include Mongo
end
klass.class_eval &block
end

View File

@ -1,58 +1,61 @@
require 'test/test_helper' require 'test/test_helper'
context "Basic operations: " do class CollectionTest < Test::Unit::TestCase
setup do
@logger = mock()
end
should "send update message" do context "Basic operations: " do
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false) setup do
@db = @conn['testing'] @logger = mock()
@coll = @db.collection('books')
@conn.expects(:send_message).with do |op, msg, log|
op == 2001 && log.include?("db.books.update")
end end
@coll.update({}, {:title => 'Moby Dick'})
end
should "send insert message" do should "send update message" do
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false) @conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
@db = @conn['testing'] @db = @conn['testing']
@coll = @db.collection('books') @coll = @db.collection('books')
@conn.expects(:send_message).with do |op, msg, log| @conn.expects(:send_message).with do |op, msg, log|
op == 2002 && log.include?("db.books.insert") op == 2001 && log.include?("db.books.update")
end
@coll.update({}, {:title => 'Moby Dick'})
end end
@coll.insert({:title => 'Moby Dick'})
end
should "not log binary data" do should "send insert message" do
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false) @conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
@db = @conn['testing'] @db = @conn['testing']
@coll = @db.collection('books') @coll = @db.collection('books')
data = Mongo::Binary.new(("BINARY " * 1000).unpack("c*")) @conn.expects(:send_message).with do |op, msg, log|
@conn.expects(:send_message).with do |op, msg, log| op == 2002 && log.include?("db.books.insert")
op == 2002 && log.include?("Mongo::Binary") end
@coll.insert({:title => 'Moby Dick'})
end end
@coll.insert({:data => data})
end
should "send safe update message" do should "not log binary data" do
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false) @conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
@db = @conn['testing'] @db = @conn['testing']
@coll = @db.collection('books') @coll = @db.collection('books')
@conn.expects(:send_message_with_safe_check).with do |op, msg, db_name, log| data = Mongo::Binary.new(("BINARY " * 1000).unpack("c*"))
op == 2001 && log.include?("db.books.update") @conn.expects(:send_message).with do |op, msg, log|
op == 2002 && log.include?("Mongo::Binary")
end
@coll.insert({:data => data})
end end
@coll.update({}, {:title => 'Moby Dick'}, :safe => true)
end
should "send safe insert message" do should "send safe update message" do
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false) @conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
@db = @conn['testing'] @db = @conn['testing']
@coll = @db.collection('books') @coll = @db.collection('books')
@conn.expects(:send_message_with_safe_check).with do |op, msg, db_name, log| @conn.expects(:send_message_with_safe_check).with do |op, msg, db_name, log|
op == 2001 && log.include?("db.books.update") op == 2001 && log.include?("db.books.update")
end
@coll.update({}, {:title => 'Moby Dick'}, :safe => true)
end
should "send safe insert message" do
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
@db = @conn['testing']
@coll = @db.collection('books')
@conn.expects(:send_message_with_safe_check).with do |op, msg, db_name, log|
op == 2001 && log.include?("db.books.update")
end
@coll.update({}, {:title => 'Moby Dick'}, :safe => true)
end end
@coll.update({}, {:title => 'Moby Dick'}, :safe => true)
end end
end end

View File

@ -1,114 +1,116 @@
require 'test/test_helper' require 'test/test_helper'
include Mongo include Mongo
context "Initialization: " do class ConnectionTest < Test::Unit::TestCase
setup do context "Initialization: " do
def new_mock_socket
socket = Object.new
socket.stubs(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket
end
def new_mock_db
db = Object.new
end
end
context "given a single node" do
setup do setup do
TCPSocket.stubs(:new).returns(new_mock_socket) def new_mock_socket
@conn = Connection.new('localhost', 27017, :connect => false) socket = Object.new
socket.stubs(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
admin_db = new_mock_db socket
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1})
@conn.expects(:[]).with('admin').returns(admin_db)
@conn.connect_to_master
end
should "set localhost and port to master" do
assert_equal 'localhost', @conn.host
assert_equal 27017, @conn.port
end
should "set connection pool to 1" do
assert_equal 1, @conn.size
end
should "default slave_ok to false" do
assert !@conn.slave_ok?
end
end
context "initializing a paired connection" do
should "require left and right nodes" do
assert_raise MongoArgumentError do
Connection.paired(['localhost', 27018], :connect => false)
end end
assert_raise MongoArgumentError do def new_mock_db
Connection.paired(['localhost', 27018], :connect => false) db = Object.new
end end
end end
should "store both nodes" do context "given a single node" do
@conn = Connection.paired([['localhost', 27017], ['localhost', 27018]], :connect => false) setup do
TCPSocket.stubs(:new).returns(new_mock_socket)
@conn = Connection.new('localhost', 27017, :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0] admin_db = new_mock_db
assert_equal ['localhost', 27018], @conn.nodes[1] admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1})
end @conn.expects(:[]).with('admin').returns(admin_db)
end @conn.connect_to_master
context "initializing with a mongodb uri" do
should "parse a simple uri" do
@conn = Connection.from_uri("mongodb://localhost", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
end
should "parse a uri specifying multiple nodes" do
@conn = Connection.from_uri("mongodb://localhost:27017,mydb.com:27018", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
assert_equal ['mydb.com', 27018], @conn.nodes[1]
end
should "parse a uri specifying multiple nodes with auth" do
@conn = Connection.from_uri("mongodb://kyle:s3cr3t@localhost:27017/app,mickey:m0u5e@mydb.com:27018/dsny", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
assert_equal ['mydb.com', 27018], @conn.nodes[1]
assert_equal ['kyle', 's3cr3t', 'app'], @conn.auths[0]
assert_equal ['mickey', 'm0u5e', 'dsny'], @conn.auths[1]
end
should "attempt to connect" do
TCPSocket.stubs(:new).returns(new_mock_socket)
@conn = Connection.from_uri("mongodb://localhost", :connect => false)
admin_db = new_mock_db
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1})
@conn.expects(:[]).with('admin').returns(admin_db)
@conn.connect_to_master
end
should "raise an error on invalid uris" do
assert_raise MongoArgumentError do
Connection.from_uri("mongo://localhost", :connect => false)
end end
assert_raise MongoArgumentError do should "set localhost and port to master" do
Connection.from_uri("mongodb://localhost:abc", :connect => false) assert_equal 'localhost', @conn.host
assert_equal 27017, @conn.port
end end
assert_raise MongoArgumentError do should "set connection pool to 1" do
Connection.from_uri("mongodb://localhost:27017, my.db.com:27018, ", :connect => false) assert_equal 1, @conn.size
end
should "default slave_ok to false" do
assert !@conn.slave_ok?
end end
end end
should "require all of username, password, and database if any one is specified" do context "initializing a paired connection" do
assert_raise MongoArgumentError do should "require left and right nodes" do
Connection.from_uri("mongodb://localhost/db", :connect => false) assert_raise MongoArgumentError do
Connection.paired(['localhost', 27018], :connect => false)
end
assert_raise MongoArgumentError do
Connection.paired(['localhost', 27018], :connect => false)
end
end end
assert_raise MongoArgumentError do should "store both nodes" do
Connection.from_uri("mongodb://kyle:password@localhost", :connect => false) @conn = Connection.paired([['localhost', 27017], ['localhost', 27018]], :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
assert_equal ['localhost', 27018], @conn.nodes[1]
end
end
context "initializing with a mongodb uri" do
should "parse a simple uri" do
@conn = Connection.from_uri("mongodb://localhost", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
end
should "parse a uri specifying multiple nodes" do
@conn = Connection.from_uri("mongodb://localhost:27017,mydb.com:27018", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
assert_equal ['mydb.com', 27018], @conn.nodes[1]
end
should "parse a uri specifying multiple nodes with auth" do
@conn = Connection.from_uri("mongodb://kyle:s3cr3t@localhost:27017/app,mickey:m0u5e@mydb.com:27018/dsny", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0]
assert_equal ['mydb.com', 27018], @conn.nodes[1]
assert_equal ['kyle', 's3cr3t', 'app'], @conn.auths[0]
assert_equal ['mickey', 'm0u5e', 'dsny'], @conn.auths[1]
end
should "attempt to connect" do
TCPSocket.stubs(:new).returns(new_mock_socket)
@conn = Connection.from_uri("mongodb://localhost", :connect => false)
admin_db = new_mock_db
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1})
@conn.expects(:[]).with('admin').returns(admin_db)
@conn.connect_to_master
end
should "raise an error on invalid uris" do
assert_raise MongoArgumentError do
Connection.from_uri("mongo://localhost", :connect => false)
end
assert_raise MongoArgumentError do
Connection.from_uri("mongodb://localhost:abc", :connect => false)
end
assert_raise MongoArgumentError do
Connection.from_uri("mongodb://localhost:27017, my.db.com:27018, ", :connect => false)
end
end
should "require all of username, password, and database if any one is specified" do
assert_raise MongoArgumentError do
Connection.from_uri("mongodb://localhost/db", :connect => false)
end
assert_raise MongoArgumentError do
Connection.from_uri("mongodb://kyle:password@localhost", :connect => false)
end
end end
end end
end end

View File

@ -1,91 +1,93 @@
require 'test/test_helper' require 'test/test_helper'
context "Cursor options" do class CursorTest < Test::Unit::TestCase
setup do context "Cursor options" do
@connection = stub(:class => Connection) setup do
@db = stub(:name => "testing", :slave_ok? => false, :connection => @connection) @connection = stub(:class => Connection)
@collection = stub(:db => @db, :name => "items") @db = stub(:name => "testing", :slave_ok? => false, :connection => @connection)
@cursor = Cursor.new(@collection) @collection = stub(:db => @db, :name => "items")
@cursor = Cursor.new(@collection)
end
should "set admin to false" do
assert_equal false, @cursor.admin
@cursor = Cursor.new(@collection, :admin => true)
assert_equal true, @cursor.admin
end
should "set selector" do
assert @cursor.selector == {}
@cursor = Cursor.new(@collection, :selector => {:name => "Jones"})
assert @cursor.selector == {:name => "Jones"}
end
should "set fields" do
assert_nil @cursor.fields
@cursor = Cursor.new(@collection, :fields => [:name, :date])
assert @cursor.fields == {:name => 1, :date => 1}
end
should "set limit" do
assert_equal 0, @cursor.limit
@cursor = Cursor.new(@collection, :limit => 10)
assert_equal 10, @cursor.limit
end
should "set skip" do
assert_equal 0, @cursor.skip
@cursor = Cursor.new(@collection, :skip => 5)
assert_equal 5, @cursor.skip
end
should "set sort order" do
assert_nil @cursor.order
@cursor = Cursor.new(@collection, :order => "last_name")
assert_equal "last_name", @cursor.order
end
should "set hint" do
assert_nil @cursor.hint
@cursor = Cursor.new(@collection, :hint => "name")
assert_equal "name", @cursor.hint
end
should "cache full collection name" do
assert_equal "testing.items", @cursor.full_collection_name
end
end end
should "set admin to false" do context "Query fields" do
assert_equal false, @cursor.admin setup do
@connection = stub(:class => Collection)
@db = stub(:slave_ok? => true, :name => "testing", :connection => @connection)
@collection = stub(:db => @db, :name => "items")
end
@cursor = Cursor.new(@collection, :admin => true) should "when an array should return a hash with each key" do
assert_equal true, @cursor.admin @cursor = Cursor.new(@collection, :fields => [:name, :age])
end result = @cursor.fields
assert_equal result.keys.sort{|a,b| a.to_s <=> b.to_s}, [:age, :name].sort{|a,b| a.to_s <=> b.to_s}
assert result.values.all? {|v| v == 1}
end
should "set selector" do should "when a string, return a hash with just the key" do
assert @cursor.selector == {} @cursor = Cursor.new(@collection, :fields => "name")
result = @cursor.fields
assert_equal result.keys.sort, ["name"]
assert result.values.all? {|v| v == 1}
end
@cursor = Cursor.new(@collection, :selector => {:name => "Jones"}) should "return nil when neither hash nor string nor symbol" do
assert @cursor.selector == {:name => "Jones"} @cursor = Cursor.new(@collection, :fields => 1234567)
end assert_nil @cursor.fields
end
should "set fields" do
assert_nil @cursor.fields
@cursor = Cursor.new(@collection, :fields => [:name, :date])
assert @cursor.fields == {:name => 1, :date => 1}
end
should "set limit" do
assert_equal 0, @cursor.limit
@cursor = Cursor.new(@collection, :limit => 10)
assert_equal 10, @cursor.limit
end
should "set skip" do
assert_equal 0, @cursor.skip
@cursor = Cursor.new(@collection, :skip => 5)
assert_equal 5, @cursor.skip
end
should "set sort order" do
assert_nil @cursor.order
@cursor = Cursor.new(@collection, :order => "last_name")
assert_equal "last_name", @cursor.order
end
should "set hint" do
assert_nil @cursor.hint
@cursor = Cursor.new(@collection, :hint => "name")
assert_equal "name", @cursor.hint
end
should "cache full collection name" do
assert_equal "testing.items", @cursor.full_collection_name
end
end
context "Query fields" do
setup do
@connection = stub(:class => Collection)
@db = stub(:slave_ok? => true, :name => "testing", :connection => @connection)
@collection = stub(:db => @db, :name => "items")
end
should "when an array should return a hash with each key" do
@cursor = Cursor.new(@collection, :fields => [:name, :age])
result = @cursor.fields
assert_equal result.keys.sort{|a,b| a.to_s <=> b.to_s}, [:age, :name].sort{|a,b| a.to_s <=> b.to_s}
assert result.values.all? {|v| v == 1}
end
should "when a string, return a hash with just the key" do
@cursor = Cursor.new(@collection, :fields => "name")
result = @cursor.fields
assert_equal result.keys.sort, ["name"]
assert result.values.all? {|v| v == 1}
end
should "return nil when neither hash nor string nor symbol" do
@cursor = Cursor.new(@collection, :fields => 1234567)
assert_nil @cursor.fields
end end
end end

View File

@ -1,98 +1,98 @@
require 'test/test_helper' require 'test/test_helper'
context "DBTest: " do class DBTest < Test::Unit::TestCase
setup do context "DBTest: " do
def insert_message(db, documents)
documents = [documents] unless documents.is_a?(Array)
message = ByteBuffer.new
message.put_int(0)
BSON.serialize_cstr(message, "#{db.name}.test")
documents.each { |doc| message.put_array(BSON.new.serialize(doc, true).to_a) }
message = db.add_message_headers(Mongo::Constants::OP_INSERT, message)
end
end
context "DB commands" do
setup do setup do
@conn = stub() def insert_message(db, documents)
@db = DB.new("testing", @conn) documents = [documents] unless documents.is_a?(Array)
@collection = mock() message = ByteBuffer.new
@db.stubs(:system_command_collection).returns(@collection) message.put_int(0)
end BSON.serialize_cstr(message, "#{db.name}.test")
documents.each { |doc| message.put_array(BSON.new.serialize(doc, true).to_a) }
should "raise an error if given a hash with more than one key" do message = db.add_message_headers(Mongo::Constants::OP_INSERT, message)
assert_raise MongoArgumentError do
@db.command(:buildinfo => 1, :somekey => 1)
end end
end end
should "raise an error if the selector is omitted" do context "DB commands" do
assert_raise MongoArgumentError do setup do
@db.command({}, true) @conn = stub()
@db = DB.new("testing", @conn)
@collection = mock()
@db.stubs(:system_command_collection).returns(@collection)
end end
end
should "create the proper cursor" do should "raise an error if given a hash with more than one key" do
@cursor = mock(:next_document => {"ok" => 1}) assert_raise MongoArgumentError do
Cursor.expects(:new).with(@collection, :admin => true, @db.command(:buildinfo => 1, :somekey => 1)
:limit => -1, :selector => {:buildinfo => 1}, :socket => nil).returns(@cursor) end
command = {:buildinfo => 1} end
@db.command(command, true)
end
should "raise an error when the command fails" do should "raise an error if the selector is omitted" do
@cursor = mock(:next_document => {"ok" => 0}) assert_raise MongoArgumentError do
Cursor.expects(:new).with(@collection, :admin => true, @db.command({}, true)
:limit => -1, :selector => {:buildinfo => 1}, :socket => nil).returns(@cursor) end
assert_raise OperationFailure do end
should "create the proper cursor" do
@cursor = mock(:next_document => {"ok" => 1})
Cursor.expects(:new).with(@collection, :admin => true,
:limit => -1, :selector => {:buildinfo => 1}, :socket => nil).returns(@cursor)
command = {:buildinfo => 1} command = {:buildinfo => 1}
@db.command(command, true, true) @db.command(command, true)
end end
end
should "raise an error if logging out fails" do should "raise an error when the command fails" do
@db.expects(:command).returns({}) @cursor = mock(:next_document => {"ok" => 0})
assert_raise MongoDBError do Cursor.expects(:new).with(@collection, :admin => true,
@db.logout :limit => -1, :selector => {:buildinfo => 1}, :socket => nil).returns(@cursor)
assert_raise OperationFailure do
command = {:buildinfo => 1}
@db.command(command, true, true)
end
end end
end
should "raise an error if collection creation fails" do should "raise an error if logging out fails" do
@db.expects(:collection_names).returns([]) @db.expects(:command).returns({})
@db.expects(:command).returns({}) assert_raise MongoDBError do
assert_raise MongoDBError do @db.logout
@db.create_collection("foo") end
end end
end
should "raise an error if getlasterror fails" do should "raise an error if collection creation fails" do
@db.expects(:command).returns({}) @db.expects(:collection_names).returns([])
assert_raise MongoDBError do @db.expects(:command).returns({})
@db.error assert_raise MongoDBError do
@db.create_collection("foo")
end
end end
end
should "raise an error if rename fails" do should "raise an error if getlasterror fails" do
@db.expects(:command).returns({}) @db.expects(:command).returns({})
assert_raise MongoDBError do assert_raise MongoDBError do
@db.rename_collection("foo", "bar") @db.error
end
end end
end
should "raise an error if drop_index fails" do should "raise an error if rename fails" do
@db.expects(:command).returns({}) @db.expects(:command).returns({})
assert_raise MongoDBError do assert_raise MongoDBError do
@db.drop_index("foo", "bar") @db.rename_collection("foo", "bar")
end
end end
end
should "raise an error if set_profiling_level fails" do should "raise an error if drop_index fails" do
@db.expects(:command).returns({}) @db.expects(:command).returns({})
assert_raise MongoDBError do assert_raise MongoDBError do
@db.profiling_level = :slow_only @db.drop_index("foo", "bar")
end
end
should "raise an error if set_profiling_level fails" do
@db.expects(:command).returns({})
assert_raise MongoDBError do
@db.profiling_level = :slow_only
end
end end
end end
end end
end end