From df91c57c312bee97a16bced1035bd704e518ac38 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Nov 2010 12:09:56 -0800 Subject: rename internal "TokyoCabinet" => "TC" It's less typing and less likely to clash on both the eyes and the interpreter. --- lib/metropolis.rb | 4 +- lib/metropolis/tc.rb | 6 + lib/metropolis/tc/hdb.rb | 160 +++++++++++++++++++++++++++ lib/metropolis/tc/hdb/ro.rb | 34 ++++++ lib/metropolis/tokyocabinet.rb | 6 - lib/metropolis/tokyocabinet/hdb.rb | 160 --------------------------- lib/metropolis/tokyocabinet/hdb/ro.rb | 34 ------ test/test_tc_hdb.rb | 200 ++++++++++++++++++++++++++++++++++ test/test_tokyocabinet_hdb.rb | 200 ---------------------------------- 9 files changed, 402 insertions(+), 402 deletions(-) create mode 100644 lib/metropolis/tc.rb create mode 100644 lib/metropolis/tc/hdb.rb create mode 100644 lib/metropolis/tc/hdb/ro.rb delete mode 100644 lib/metropolis/tokyocabinet.rb delete mode 100644 lib/metropolis/tokyocabinet/hdb.rb delete mode 100644 lib/metropolis/tokyocabinet/hdb/ro.rb create mode 100644 test/test_tc_hdb.rb delete mode 100644 test/test_tokyocabinet_hdb.rb diff --git a/lib/metropolis.rb b/lib/metropolis.rb index 987040d..05dc071 100644 --- a/lib/metropolis.rb +++ b/lib/metropolis.rb @@ -3,7 +3,7 @@ require 'rack' require 'uri' module Metropolis - autoload :TokyoCabinet, 'metropolis/tokyocabinet' + autoload :TC, 'metropolis/tc' def self.new(opts = {}) opts = opts.dup @@ -15,7 +15,7 @@ module Metropolis opts[:query] = Rack::Utils.parse_query(uri.query) if uri.query case ext = File.extname(uri.path) when '.tch' - rv.extend Metropolis::TokyoCabinet::HDB + rv.extend Metropolis::TC::HDB else raise ArgumentError, "unsupported suffix: #{ext}" end diff --git a/lib/metropolis/tc.rb b/lib/metropolis/tc.rb new file mode 100644 index 0000000..84dfaff --- /dev/null +++ b/lib/metropolis/tc.rb @@ -0,0 +1,6 @@ +# -*- encoding: binary -*- +require 'tokyocabinet' + +module Metropolis::TC + autoload :HDB, 'metropolis/tc/hdb' +end diff --git a/lib/metropolis/tc/hdb.rb b/lib/metropolis/tc/hdb.rb new file mode 100644 index 0000000..ec387b4 --- /dev/null +++ b/lib/metropolis/tc/hdb.rb @@ -0,0 +1,160 @@ +# -*- encoding: binary -*- + +# this module is NOT thread-safe, all performance is dependent on the +# local machine so there is never anything that needs yielding to threads. +module Metropolis::TC::HDB + autoload :RO, 'metropolis/tc/hdb/ro' + + TCHDB = TokyoCabinet::HDB # :nodoc + include Rack::Utils # unescape + + def r(code) + body = "#{HTTP_STATUS_CODES[code]}\n" + [ code, + { 'Content-Length' => body.size.to_s, 'Content-Type' => 'text/plain' }, + [ body ] ] + end + + def setup(opts) + @headers = { 'Content-Type' => 'application/octet-stream' } + @headers.merge!(opts[:response_headers] || {}) + @nr_slots = opts[:nr_slots] || 3 + path_pattern = opts[:path_pattern] + path_pattern.scan(/%\d*x/).size == 1 or + raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}" + @optimize = nil + if query = opts[:query] + flags = 0 + @optimize = %w(bnum apow fpow).map do |x| + v = query[x] + v ? v.to_i : nil + end + case large = query['large'] + when 'false', nil + when 'true' + flags |= TCHDB::TLARGE + else + raise ArgumentError, "invalid 'large' value: #{large}" + end + case compress = query['compress'] + when nil + when 'deflate', 'bzip', 'tcbs' + flags |= TCHDB.const_get("T#{compress.upcase}") + else + raise ArgumentError, "invalid 'compress' value: #{compress}" + end + @optimize << flags + end + @dbv = (0...@nr_slots).to_a.map do |slot| + path = sprintf(path_pattern, slot) + hdb = TCHDB.new + unless opts[:read_only] + hdb.open(path, TCHDB::OWRITER | TCHDB::OCREAT) or ex!(:open, hdb) + if @optimize + hdb.optimize(*@optimize) or ex!(:optimize, hdb) + end + hdb.close or ex!(:close, hdb) + end + [ hdb, path ] + end + @rd_flags = TCHDB::OREADER + @wr_flags = TCHDB::OWRITER + if opts[:read_only] + extend(RO) + end + end + + def call(env) + if %r{\A/(.*)\z} =~ env["PATH_INFO"] + key = unescape($1) + case env["REQUEST_METHOD"] + when "GET" + get(key) + when "HEAD" + head(key) + when "DELETE" + delete(key) + when "PUT" + put(key, env) + else + [ 405, {}, [] ] + end + else # OPTIONS + [ 405, {}, [] ] + end + end + + def ex!(msg, hdb) + raise "#{msg}: #{hdb.errmsg(hdb.ecode)}" + end + + def writer(key, &block) + hdb, path = @dbv[key.hash % @nr_slots] + hdb.open(path, @wr_flags) or ex!(:open, hdb) + yield hdb + ensure + hdb.close or ex!(:close, hdb) + end + + def reader(key) + hdb, path = @dbv[key.hash % @nr_slots] + hdb.open(path, @rd_flags) or ex!(:open, hdb) + yield hdb + ensure + hdb.close or ex!(:close, hdb) + end + + def put(key, env) + value = env["rack.input"].read + writer(key) do |hdb| + case env['HTTP_X_TT_PDMODE'] + when '1' + unless hdb.putkeep(key, value) + TCHDB::EKEEP == hdb.ecode and return r(409) + ex!(:putkeep, hdb) + end + when '2' + hdb.putcat(key, value) or ex!(:putcat, hdb) + else + # ttserver does not care for other PDMODE values, so we don't, either + hdb.put(key, value) or ex!(:put, hdb) + end + end + r(201) + end + + def delete(key) + writer(key) do |hdb| + unless hdb.delete(key) + TCHDB::ENOREC == hdb.ecode and return r(404) + ex!(:delete, hdb) + end + end + r(200) + end + + def head(key) + size = reader(key) { |hdb| hdb.vsiz(key) or ex!(:vsiz, hdb) } + 0 > size and return r(404) + [ 200, { + 'Content-Length' => size.to_s, + }.merge!(@headers), [] ] + end + + def get(key) + value = nil + reader(key) do |hdb| + unless value = hdb.get(key) + TCHDB::ENOREC == hdb.ecode and return r(404) + ex!(:get, hdb) + end + end + [ 200, { + 'Content-Length' => value.size.to_s, + }.merge!(@headers), [ value ] ] + end + + def close! + @dbv.each { |(hdb,_)| hdb.close } + end +end diff --git a/lib/metropolis/tc/hdb/ro.rb b/lib/metropolis/tc/hdb/ro.rb new file mode 100644 index 0000000..f348ee4 --- /dev/null +++ b/lib/metropolis/tc/hdb/ro.rb @@ -0,0 +1,34 @@ +# -*- encoding: binary -*- + +module Metropolis::TC::HDB::RO + def self.extended(obj) + obj.instance_eval do + @wr_flags = nil + @rd_flags |= TokyoCabinet::HDB::ONOLCK + @dbv.each { |(hdb, path)| + hdb.open(path, @rd_flags) or ex!(:open, path) + } + @ro_dbv = @dbv.map { |(hdb,_)| hdb } + end + end + + def call(env) + if %r{\A/(.*)\z} =~ env["PATH_INFO"] + key = unescape($1) + case env["REQUEST_METHOD"] + when "GET" + get(key) + when "HEAD" + head(key) + else + [ 405, {}, [] ] + end + else # OPTIONS + [ 405, {}, [] ] + end + end + + def reader(key) + yield @ro_dbv[key.hash % @nr_slots] + end +end diff --git a/lib/metropolis/tokyocabinet.rb b/lib/metropolis/tokyocabinet.rb deleted file mode 100644 index 86b8104..0000000 --- a/lib/metropolis/tokyocabinet.rb +++ /dev/null @@ -1,6 +0,0 @@ -# -*- encoding: binary -*- -require 'tokyocabinet' - -module Metropolis::TokyoCabinet - autoload :HDB, 'metropolis/tokyocabinet/hdb' -end diff --git a/lib/metropolis/tokyocabinet/hdb.rb b/lib/metropolis/tokyocabinet/hdb.rb deleted file mode 100644 index 513d215..0000000 --- a/lib/metropolis/tokyocabinet/hdb.rb +++ /dev/null @@ -1,160 +0,0 @@ -# -*- encoding: binary -*- - -# this module is NOT thread-safe, all performance is dependent on the -# local machine so there is never anything that needs yielding to threads. -module Metropolis::TokyoCabinet::HDB - autoload :RO, 'metropolis/tokyocabinet/hdb/ro' - - TCHDB = ::TokyoCabinet::HDB # :nodoc - include Rack::Utils # unescape - - def r(code) - body = "#{HTTP_STATUS_CODES[code]}\n" - [ code, - { 'Content-Length' => body.size.to_s, 'Content-Type' => 'text/plain' }, - [ body ] ] - end - - def setup(opts) - @headers = { 'Content-Type' => 'application/octet-stream' } - @headers.merge!(opts[:response_headers] || {}) - @nr_slots = opts[:nr_slots] || 3 - path_pattern = opts[:path_pattern] - path_pattern.scan(/%\d*x/).size == 1 or - raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}" - @optimize = nil - if query = opts[:query] - flags = 0 - @optimize = %w(bnum apow fpow).map do |x| - v = query[x] - v ? v.to_i : nil - end - case large = query['large'] - when 'false', nil - when 'true' - flags |= TCHDB::TLARGE - else - raise ArgumentError, "invalid 'large' value: #{large}" - end - case compress = query['compress'] - when nil - when 'deflate', 'bzip', 'tcbs' - flags |= TCHDB.const_get("T#{compress.upcase}") - else - raise ArgumentError, "invalid 'compress' value: #{compress}" - end - @optimize << flags - end - @dbv = (0...@nr_slots).to_a.map do |slot| - path = sprintf(path_pattern, slot) - hdb = TCHDB.new - unless opts[:read_only] - hdb.open(path, TCHDB::OWRITER | TCHDB::OCREAT) or ex!(:open, hdb) - if @optimize - hdb.optimize(*@optimize) or ex!(:optimize, hdb) - end - hdb.close or ex!(:close, hdb) - end - [ hdb, path ] - end - @rd_flags = TCHDB::OREADER - @wr_flags = TCHDB::OWRITER - if opts[:read_only] - extend(RO) - end - end - - def call(env) - if %r{\A/(.*)\z} =~ env["PATH_INFO"] - key = unescape($1) - case env["REQUEST_METHOD"] - when "GET" - get(key) - when "HEAD" - head(key) - when "DELETE" - delete(key) - when "PUT" - put(key, env) - else - [ 405, {}, [] ] - end - else # OPTIONS - [ 405, {}, [] ] - end - end - - def ex!(msg, hdb) - raise "#{msg}: #{hdb.errmsg(hdb.ecode)}" - end - - def writer(key, &block) - hdb, path = @dbv[key.hash % @nr_slots] - hdb.open(path, @wr_flags) or ex!(:open, hdb) - yield hdb - ensure - hdb.close or ex!(:close, hdb) - end - - def reader(key) - hdb, path = @dbv[key.hash % @nr_slots] - hdb.open(path, @rd_flags) or ex!(:open, hdb) - yield hdb - ensure - hdb.close or ex!(:close, hdb) - end - - def put(key, env) - value = env["rack.input"].read - writer(key) do |hdb| - case env['HTTP_X_TT_PDMODE'] - when '1' - unless hdb.putkeep(key, value) - TCHDB::EKEEP == hdb.ecode and return r(409) - ex!(:putkeep, hdb) - end - when '2' - hdb.putcat(key, value) or ex!(:putcat, hdb) - else - # ttserver does not care for other PDMODE values, so we don't, either - hdb.put(key, value) or ex!(:put, hdb) - end - end - r(201) - end - - def delete(key) - writer(key) do |hdb| - unless hdb.delete(key) - TCHDB::ENOREC == hdb.ecode and return r(404) - ex!(:delete, hdb) - end - end - r(200) - end - - def head(key) - size = reader(key) { |hdb| hdb.vsiz(key) or ex!(:vsiz, hdb) } - 0 > size and return r(404) - [ 200, { - 'Content-Length' => size.to_s, - }.merge!(@headers), [] ] - end - - def get(key) - value = nil - reader(key) do |hdb| - unless value = hdb.get(key) - TCHDB::ENOREC == hdb.ecode and return r(404) - ex!(:get, hdb) - end - end - [ 200, { - 'Content-Length' => value.size.to_s, - }.merge!(@headers), [ value ] ] - end - - def close! - @dbv.each { |(hdb,_)| hdb.close } - end -end diff --git a/lib/metropolis/tokyocabinet/hdb/ro.rb b/lib/metropolis/tokyocabinet/hdb/ro.rb deleted file mode 100644 index 89b4dd6..0000000 --- a/lib/metropolis/tokyocabinet/hdb/ro.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -*- encoding: binary -*- - -module Metropolis::TokyoCabinet::HDB::RO - def self.extended(obj) - obj.instance_eval do - @wr_flags = nil - @rd_flags |= ::TokyoCabinet::HDB::ONOLCK - @dbv.each { |(hdb, path)| - hdb.open(path, @rd_flags) or ex!(:open, path) - } - @ro_dbv = @dbv.map { |(hdb,_)| hdb } - end - end - - def call(env) - if %r{\A/(.*)\z} =~ env["PATH_INFO"] - key = unescape($1) - case env["REQUEST_METHOD"] - when "GET" - get(key) - when "HEAD" - head(key) - else - [ 405, {}, [] ] - end - else # OPTIONS - [ 405, {}, [] ] - end - end - - def reader(key) - yield @ro_dbv[key.hash % @nr_slots] - end -end diff --git a/test/test_tc_hdb.rb b/test/test_tc_hdb.rb new file mode 100644 index 0000000..21d89be --- /dev/null +++ b/test/test_tc_hdb.rb @@ -0,0 +1,200 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +require 'stringio' +require 'tokyocabinet' # FIXME: emits warning with 1.29 gem +$-w = true +require 'metropolis' + +class Test_TC_HDB < Test::Unit::TestCase + attr_reader :tmp, :o, :uri + + def setup + tmp = Tempfile.new('tchdb') + @path_pattern = tmp.path + ".%01x.tch" + tmp.close! + @uri = "tc://#{@path_pattern}" + end + + def teardown + Dir[@path_pattern.sub!(/%\d*x/, '*')].each { |x| File.unlink(x) } + end + + def osetup + o = Object.new + o.extend Metropolis::TC::HDB + assert_nothing_raised do + o.setup :path_pattern => @path_pattern + end + o + end + + def test_create_put_get_delete + o = osetup + r = o.put('hello', { 'rack.input' => StringIO.new('world') }) + assert_equal 201, r[0].to_i + assert_equal 'text/plain', r[1]['Content-Type'] + assert_equal '8', r[1]['Content-Length'] + assert_equal "Created\n", r[2].join('') + + r = o.put('hellox', { 'rack.input' => StringIO.new('worldx') }) + assert_equal 201, r[0].to_i + assert_equal 'text/plain', r[1]['Content-Type'] + assert_equal '8', r[1]['Content-Length'] + assert_equal "Created\n", r[2].join('') + + r = o.get('hello') + assert_equal 200, r[0].to_i + assert_equal 'application/octet-stream', r[1]['Content-Type'] + assert_equal '5', r[1]['Content-Length'] + assert_equal %w(world), r[2] + + r = o.head('hello') + assert_equal 200, r[0].to_i + assert_equal 'application/octet-stream', r[1]['Content-Type'] + assert_equal '5', r[1]['Content-Length'] + assert_equal [], r[2] + + r = o.get('hellox') + assert_equal 200, r[0].to_i + assert_equal 'application/octet-stream', r[1]['Content-Type'] + assert_equal '6', r[1]['Content-Length'] + assert_equal %w(worldx), r[2] + + r = o.delete('hellox') + assert_equal 200, r[0].to_i + assert_equal 'text/plain', r[1]['Content-Type'] + assert_equal '3', r[1]['Content-Length'] + assert_equal "OK\n", r[2].join('') + + r = o.delete('hellox') + assert_equal 404, r[0].to_i + assert_equal 'text/plain', r[1]['Content-Type'] + assert_equal '10', r[1]['Content-Length'] + assert_equal "Not Found\n", r[2].join('') + + r = o.get('hellox') + assert_equal 404, r[0].to_i + assert_equal 'text/plain', r[1]['Content-Type'] + assert_equal '10', r[1]['Content-Length'] + assert_equal "Not Found\n", r[2].join('') + + r = o.head('hellox') + assert_equal 404, r[0].to_i + assert_equal 'text/plain', r[1]['Content-Type'] + assert_equal '10', r[1]['Content-Length'] + assert_equal "Not Found\n", r[2].join('') + end + + def test_putkeep + o = osetup + env = { + "rack.input" => StringIO.new("hello"), + "HTTP_X_TT_PDMODE" => "1" + } + assert_equal 201, o.put("x", env)[0] + env["rack.input"] = StringIO.new("wrong") + assert_equal 409, o.put("x", env)[0] + assert_equal "hello", o.get("x")[2].join('') + end + + def test_putcat + o = osetup + env = { + "rack.input" => StringIO.new("hello"), + "HTTP_X_TT_PDMODE" => "2" + } + assert_equal 201, o.put("x", env)[0] + env["rack.input"] = StringIO.new("MOAR") + assert_equal 201, o.put("x", env)[0] + assert_equal "helloMOAR", o.get("x")[2].join('') + end + + def test_multiproc + nr = 2 + key = "k" + str = "." * (1024 * 1024) + nr.times { + fork { + o = osetup + sio = StringIO.new(str) + env = { "rack.input" => sio } + 100.times { + o.put(key, env) + sio.rewind + o.get(key) + } + } + } + res = Process.waitall + assert_equal nr, res.size + res.each { |(pid, status)| assert status.success? } + end + + def test_readonly + key = "x" + wr = osetup + wr.put(key, { "rack.input" => StringIO.new("OK") }) + o = Object.new + o.extend Metropolis::TC::HDB + assert_nothing_raised do + o.setup :path_pattern => @path_pattern, :read_only => true + end + %w(PUT DELETE).each do |rm| + env = { + "rack.input" => StringIO.new("FAIL"), + "REQUEST_METHOD" => rm, + "PATH_INFO" => "/#{key}" + } + assert_equal 405, o.call(env)[0] + end + env = { + "REQUEST_METHOD" => "GET", + "PATH_INFO" => "/#{key}", + } + assert_equal 200, o.call(env)[0] + assert_equal '2', o.call(env)[1]["Content-Length"] + assert_equal 'application/octet-stream', o.call(env)[1]["Content-Type"] + assert_equal "OK", o.call(env)[2].join('') + + env["REQUEST_METHOD"] = "HEAD" + assert_equal 200, o.call(env)[0] + assert_equal '2', o.call(env)[1]["Content-Length"] + assert_equal 'application/octet-stream', o.call(env)[1]["Content-Type"] + assert_equal "", o.call(env)[2].join('') + end + + def test_create_toplevel + k = "x" + nr_bytes = 1024 * 1024 * 20 + data = "0" * nr_bytes + obj = nil + assert_nothing_raised { obj = Metropolis.new(:uri => uri) } + + query = "large=true&apow=3&bnum=65536&compress=deflate" + assert_nothing_raised { + obj = Metropolis.new(:uri => "#{uri}?#{query}") + } + optimize_args = obj.instance_variable_get(:@optimize) + flags = TokyoCabinet::HDB::TLARGE | TokyoCabinet::HDB::TDEFLATE + assert_equal flags, optimize_args[3] + assert_equal 65536, optimize_args[0] + assert_nil optimize_args[2] + assert_equal 3, optimize_args[1] + assert_nothing_raised { obj.get(k) } + assert_nothing_raised { obj.put(k,{'rack.input' => StringIO.new(data)}) } + + obj = Metropolis.new(:uri => "#{uri}?#{query}", :read_only => true) + assert_equal data, obj.get(k)[2].join('') + obj.close! + + obj = Metropolis.new(:uri => uri, :read_only => true) + assert_equal data, obj.get(k)[2].join('') + obj.close! + sum = obj.instance_eval { + @dbv.inject(0) { |size, (hdb,path)| size += File.stat(path).size } + } + assert sum <= nr_bytes, "#{sum} > #{nr_bytes}" + obj.close! + end +end diff --git a/test/test_tokyocabinet_hdb.rb b/test/test_tokyocabinet_hdb.rb deleted file mode 100644 index 1deea1c..0000000 --- a/test/test_tokyocabinet_hdb.rb +++ /dev/null @@ -1,200 +0,0 @@ -# -*- encoding: binary -*- -require 'test/unit' -require 'tempfile' -require 'stringio' -require 'tokyocabinet' # FIXME: emits warning with 1.29 gem -$-w = true -require 'metropolis' - -class TestTokyocabinetHDB < Test::Unit::TestCase - attr_reader :tmp, :o, :uri - - def setup - tmp = Tempfile.new('tchdb') - @path_pattern = tmp.path + ".%01x.tch" - tmp.close! - @uri = "tc://#{@path_pattern}" - end - - def teardown - Dir[@path_pattern.sub!(/%\d*x/, '*')].each { |x| File.unlink(x) } - end - - def osetup - o = Object.new - o.extend Metropolis::TokyoCabinet::HDB - assert_nothing_raised do - o.setup :path_pattern => @path_pattern - end - o - end - - def test_create_put_get_delete - o = osetup - r = o.put('hello', { 'rack.input' => StringIO.new('world') }) - assert_equal 201, r[0].to_i - assert_equal 'text/plain', r[1]['Content-Type'] - assert_equal '8', r[1]['Content-Length'] - assert_equal "Created\n", r[2].join('') - - r = o.put('hellox', { 'rack.input' => StringIO.new('worldx') }) - assert_equal 201, r[0].to_i - assert_equal 'text/plain', r[1]['Content-Type'] - assert_equal '8', r[1]['Content-Length'] - assert_equal "Created\n", r[2].join('') - - r = o.get('hello') - assert_equal 200, r[0].to_i - assert_equal 'application/octet-stream', r[1]['Content-Type'] - assert_equal '5', r[1]['Content-Length'] - assert_equal %w(world), r[2] - - r = o.head('hello') - assert_equal 200, r[0].to_i - assert_equal 'application/octet-stream', r[1]['Content-Type'] - assert_equal '5', r[1]['Content-Length'] - assert_equal [], r[2] - - r = o.get('hellox') - assert_equal 200, r[0].to_i - assert_equal 'application/octet-stream', r[1]['Content-Type'] - assert_equal '6', r[1]['Content-Length'] - assert_equal %w(worldx), r[2] - - r = o.delete('hellox') - assert_equal 200, r[0].to_i - assert_equal 'text/plain', r[1]['Content-Type'] - assert_equal '3', r[1]['Content-Length'] - assert_equal "OK\n", r[2].join('') - - r = o.delete('hellox') - assert_equal 404, r[0].to_i - assert_equal 'text/plain', r[1]['Content-Type'] - assert_equal '10', r[1]['Content-Length'] - assert_equal "Not Found\n", r[2].join('') - - r = o.get('hellox') - assert_equal 404, r[0].to_i - assert_equal 'text/plain', r[1]['Content-Type'] - assert_equal '10', r[1]['Content-Length'] - assert_equal "Not Found\n", r[2].join('') - - r = o.head('hellox') - assert_equal 404, r[0].to_i - assert_equal 'text/plain', r[1]['Content-Type'] - assert_equal '10', r[1]['Content-Length'] - assert_equal "Not Found\n", r[2].join('') - end - - def test_putkeep - o = osetup - env = { - "rack.input" => StringIO.new("hello"), - "HTTP_X_TT_PDMODE" => "1" - } - assert_equal 201, o.put("x", env)[0] - env["rack.input"] = StringIO.new("wrong") - assert_equal 409, o.put("x", env)[0] - assert_equal "hello", o.get("x")[2].join('') - end - - def test_putcat - o = osetup - env = { - "rack.input" => StringIO.new("hello"), - "HTTP_X_TT_PDMODE" => "2" - } - assert_equal 201, o.put("x", env)[0] - env["rack.input"] = StringIO.new("MOAR") - assert_equal 201, o.put("x", env)[0] - assert_equal "helloMOAR", o.get("x")[2].join('') - end - - def test_multiproc - nr = 2 - key = "k" - str = "." * (1024 * 1024) - nr.times { - fork { - o = osetup - sio = StringIO.new(str) - env = { "rack.input" => sio } - 100.times { - o.put(key, env) - sio.rewind - o.get(key) - } - } - } - res = Process.waitall - assert_equal nr, res.size - res.each { |(pid, status)| assert status.success? } - end - - def test_readonly - key = "x" - wr = osetup - wr.put(key, { "rack.input" => StringIO.new("OK") }) - o = Object.new - o.extend Metropolis::TokyoCabinet::HDB - assert_nothing_raised do - o.setup :path_pattern => @path_pattern, :read_only => true - end - %w(PUT DELETE).each do |rm| - env = { - "rack.input" => StringIO.new("FAIL"), - "REQUEST_METHOD" => rm, - "PATH_INFO" => "/#{key}" - } - assert_equal 405, o.call(env)[0] - end - env = { - "REQUEST_METHOD" => "GET", - "PATH_INFO" => "/#{key}", - } - assert_equal 200, o.call(env)[0] - assert_equal '2', o.call(env)[1]["Content-Length"] - assert_equal 'application/octet-stream', o.call(env)[1]["Content-Type"] - assert_equal "OK", o.call(env)[2].join('') - - env["REQUEST_METHOD"] = "HEAD" - assert_equal 200, o.call(env)[0] - assert_equal '2', o.call(env)[1]["Content-Length"] - assert_equal 'application/octet-stream', o.call(env)[1]["Content-Type"] - assert_equal "", o.call(env)[2].join('') - end - - def test_create_toplevel - k = "x" - nr_bytes = 1024 * 1024 * 20 - data = "0" * nr_bytes - obj = nil - assert_nothing_raised { obj = Metropolis.new(:uri => uri) } - - query = "large=true&apow=3&bnum=65536&compress=deflate" - assert_nothing_raised { - obj = Metropolis.new(:uri => "#{uri}?#{query}") - } - optimize_args = obj.instance_variable_get(:@optimize) - flags = ::TokyoCabinet::HDB::TLARGE | ::TokyoCabinet::HDB::TDEFLATE - assert_equal flags, optimize_args[3] - assert_equal 65536, optimize_args[0] - assert_nil optimize_args[2] - assert_equal 3, optimize_args[1] - assert_nothing_raised { obj.get(k) } - assert_nothing_raised { obj.put(k,{'rack.input' => StringIO.new(data)}) } - - obj = Metropolis.new(:uri => "#{uri}?#{query}", :read_only => true) - assert_equal data, obj.get(k)[2].join('') - obj.close! - - obj = Metropolis.new(:uri => uri, :read_only => true) - assert_equal data, obj.get(k)[2].join('') - obj.close! - sum = obj.instance_eval { - @dbv.inject(0) { |size, (hdb,path)| size += File.stat(path).size } - } - assert sum <= nr_bytes, "#{sum} > #{nr_bytes}" - obj.close! - end -end -- cgit v1.2.3-24-ge0c7