From 2283b99834c6d688826b80bf2d14d5608f6d18ad Mon Sep 17 00:00:00 2001 From: Alex Young Date: Tue, 3 Jul 2012 13:33:52 +0100 Subject: [PATCH] Split acceptance tests into separate files --- tests/acceptance/environment.rb | 131 +++++++ .../{test_file_writer.rb => file_writer.rb} | 38 +- tests/acceptance/nbd_scenarios | 364 +----------------- tests/acceptance/test_dest_error_handling.rb | 65 ++++ tests/acceptance/test_happy_path.rb | 84 ++++ .../acceptance/test_source_error_handling.rb | 95 +++++ 6 files changed, 397 insertions(+), 380 deletions(-) create mode 100644 tests/acceptance/environment.rb rename tests/acceptance/{test_file_writer.rb => file_writer.rb} (86%) create mode 100644 tests/acceptance/test_dest_error_handling.rb create mode 100644 tests/acceptance/test_happy_path.rb create mode 100644 tests/acceptance/test_source_error_handling.rb diff --git a/tests/acceptance/environment.rb b/tests/acceptance/environment.rb new file mode 100644 index 0000000..131a439 --- /dev/null +++ b/tests/acceptance/environment.rb @@ -0,0 +1,131 @@ +# encoding: utf-8 + +require 'flexnbd' +require 'file_writer' + +class Environment + attr_reader( :blocksize, :filename1, :filename2, :ip, + :port1, :port2, :nbd1, :nbd2, :file1, :file2 ) + + def initialize + @blocksize = 1024 + @filename1 = "/tmp/.flexnbd.test.#{$$}.#{Time.now.to_i}.1" + @filename2 = "/tmp/.flexnbd.test.#{$$}.#{Time.now.to_i}.2" + @ip = "127.0.0.1" + @available_ports = [*40000..41000] - listening_ports + @port1 = @available_ports.shift + @port2 = @available_ports.shift + @nbd1 = FlexNBD.new("../../build/flexnbd", @ip, @port1) + @nbd2 = FlexNBD.new("../../build/flexnbd", @ip, @port2) + + @fake_pid = nil + end + + + def serve1(*acl) + @nbd1.serve(@filename1, *acl) + end + + def serve2(*acl) + @nbd2.serve(@filename2, *acl) + end + + + def listen1( *acl ) + @nbd1.listen( @filename1, *(acl.empty? ? @acl1: acl) ) + end + + def listen2( *acl ) + @nbd2.listen( @filename2, *acl ) + end + + + def acl1( *acl ) + @nbd1.acl( *acl ) + end + + def acl2( *acl ) + @nbd2.acl( *acl ) + end + + + def status1 + @nbd1.status.first + end + + def status2 + @nbd2.status.first + end + + + + def mirror12 + @nbd1.mirror( @nbd2.ip, @nbd2.port ) + end + + def mirror12_unchecked + @nbd1.mirror_unchecked( @nbd2.ip, @nbd2.port, nil, nil, 10 ) + end + + + def writefile1(data) + @file1 = FileWriter.new(@filename1, @blocksize).write(data) + end + + def writefile2(data) + @file2 = FileWriter.new(@filename2, @blocksize).write(data) + end + + + + def listening_ports + `netstat -ltn`. + split("\n"). + map { |x| x.split(/\s+/) }[2..-1]. + map { |l| l[3].split(":")[-1].to_i } + end + + + def cleanup + if @fake_pid + begin + Process.waitpid2( @fake_pid ) + rescue Errno::ESRCH + end + end + + + @nbd1.kill + @nbd2.kill + + [@filename1, @filename2].each do |f| + File.unlink(f) if File.exists?(f) + end + end + + + def run_fake( name, addr, port ) + fakedir = File.join( File.dirname( __FILE__ ), "fakes" ) + fake = Dir[File.join( fakedir, name ) + "*"].sort.find { |fn| + File.executable?( fn ) + } + + raise "no fake executable" unless fake + raise "no addr" unless addr + raise "no port" unless port + @fake_pid = fork do + exec fake + " " + addr.to_s + " " + port.to_s + end + sleep(0.5) + end + + + def fake_reports_success + _,status = Process.waitpid2( @fake_pid ) + @fake_pid = nil + status.success? + end + + +end # class Environment + diff --git a/tests/acceptance/test_file_writer.rb b/tests/acceptance/file_writer.rb similarity index 86% rename from tests/acceptance/test_file_writer.rb rename to tests/acceptance/file_writer.rb index 025e340..8c20f53 100644 --- a/tests/acceptance/test_file_writer.rb +++ b/tests/acceptance/file_writer.rb @@ -1,21 +1,21 @@ # Noddy test class for writing files to disc in predictable patterns # in order to test FlexNBD. # -class TestFileWriter +class FileWriter def initialize(filename, blocksize) @fh = File.open(filename, "w+") @blocksize = blocksize @pattern = "" end - - # We write in fixed block sizes, given by "blocksize" + + # We write in fixed block sizes, given by "blocksize" # _ means skip a block # 0 means write a block full of zeroes # f means write a block with the file offset packed every 4 bytes - # + # def write(data) @pattern += data - + data.split("").each do |code| if code == "_" @fh.seek(@blocksize, IO::SEEK_CUR) @@ -26,8 +26,8 @@ class TestFileWriter @fh.flush self end - - + + # Returns what the data ought to be at the given offset and length # def read_original( off, len ) @@ -37,25 +37,25 @@ class TestFileWriter data(blk, blk_off) }.join[off...(off+len)] end - + # Read what's actually in the file # def read(off, len) @fh.seek(off, IO::SEEK_SET) @fh.read(len) end - + def untouched?(offset, len) read(offset, len) == read_original(offset, len) end - + def close @fh.close nil end - + protected - + def data(code, at=@fh.tell) case code when "0", "_" @@ -71,20 +71,20 @@ class TestFileWriter r else raise "Unknown character '#{block}'" - end + end end - + end if __FILE__==$0 require 'tempfile' require 'test/unit' - class TestFileWriterTest < Test::Unit::TestCase + class FileWriterTest < Test::Unit::TestCase def test_read_original_zeros Tempfile.open("test_read_original_zeros") do |tempfile| tempfile.close - file = TestFileWriter.new( tempfile.path, 4096 ) + file = FileWriter.new( tempfile.path, 4096 ) file.write( "0" ) assert_equal file.read( 0, 4096 ), file.read_original( 0, 4096 ) assert( file.untouched?(0,4096) , "Untouched file was touched." ) @@ -94,7 +94,7 @@ if __FILE__==$0 def test_read_original_offsets Tempfile.open("test_read_original_offsets") do |tempfile| tempfile.close - file = TestFileWriter.new( tempfile.path, 4096 ) + file = FileWriter.new( tempfile.path, 4096 ) file.write( "f" ) assert_equal file.read( 0, 4096 ), file.read_original( 0, 4096 ) assert( file.untouched?(0,4096) , "Untouched file was touched." ) @@ -104,7 +104,7 @@ if __FILE__==$0 def test_file_size Tempfile.open("test_file_size") do |tempfile| tempfile.close - file = TestFileWriter.new( tempfile.path, 4096 ) + file = FileWriter.new( tempfile.path, 4096 ) file.write( "f" ) assert_equal 4096, File.stat( tempfile.path ).size end @@ -113,7 +113,7 @@ if __FILE__==$0 def test_read_original_size Tempfile.open("test_read_original_offsets") do |tempfile| tempfile.close - file = TestFileWriter.new( tempfile.path, 4) + file = FileWriter.new( tempfile.path, 4) file.write( "f"*4 ) assert_equal 4, file.read_original(0, 4).length end diff --git a/tests/acceptance/nbd_scenarios b/tests/acceptance/nbd_scenarios index 7665453..039cee5 100644 --- a/tests/acceptance/nbd_scenarios +++ b/tests/acceptance/nbd_scenarios @@ -1,364 +1,6 @@ #!/usr/bin/ruby -require 'test/unit' -require 'flexnbd' -require 'test_file_writer' - -class Environment - attr_reader( :blocksize, :filename1, :filename2, :ip, - :port1, :port2, :nbd1, :nbd2, :file1, :file2 ) - - def initialize - @blocksize = 1024 - @filename1 = "/tmp/.flexnbd.test.#{$$}.#{Time.now.to_i}.1" - @filename2 = "/tmp/.flexnbd.test.#{$$}.#{Time.now.to_i}.2" - @ip = "127.0.0.1" - @available_ports = [*40000..41000] - listening_ports - @port1 = @available_ports.shift - @port2 = @available_ports.shift - @nbd1 = FlexNBD.new("../../build/flexnbd", @ip, @port1) - @nbd2 = FlexNBD.new("../../build/flexnbd", @ip, @port2) - - @fake_pid = nil - end - - - def serve1(*acl) - @nbd1.serve(@filename1, *acl) - end - - def serve2(*acl) - @nbd2.serve(@filename2, *acl) - end - - - def listen1( *acl ) - @nbd1.listen( @filename1, *(acl.empty? ? @acl1: acl) ) - end - - def listen2( *acl ) - @nbd2.listen( @filename2, *acl ) - end - - - def acl1( *acl ) - @nbd1.acl( *acl ) - end - - def acl2( *acl ) - @nbd2.acl( *acl ) - end - - - def status1 - @nbd1.status.first - end - - def status2 - @nbd2.status.first - end - - - - def mirror12 - @nbd1.mirror( @nbd2.ip, @nbd2.port ) - end - - def mirror12_unchecked - @nbd1.mirror_unchecked( @nbd2.ip, @nbd2.port, nil, nil, 10 ) - end - - - def writefile1(data) - @file1 = TestFileWriter.new(@filename1, @blocksize).write(data) - end - - def writefile2(data) - @file2 = TestFileWriter.new(@filename2, @blocksize).write(data) - end - - - - def listening_ports - `netstat -ltn`. - split("\n"). - map { |x| x.split(/\s+/) }[2..-1]. - map { |l| l[3].split(":")[-1].to_i } - end - - - def cleanup - if @fake_pid - begin - Process.waitpid2( @fake_pid ) - rescue Errno::ESRCH - end - end - - - @nbd1.kill - @nbd2.kill - - [@filename1, @filename2].each do |f| - File.unlink(f) if File.exists?(f) - end - end - - - def run_fake( name, addr, port ) - fakedir = File.join( File.dirname( __FILE__ ), "fakes" ) - fake = Dir[File.join( fakedir, name ) + "*"].sort.find { |fn| - File.executable?( fn ) - } - - raise "no fake executable" unless fake - raise "no addr" unless addr - raise "no port" unless port - @fake_pid = fork do - exec fake + " " + addr.to_s + " " + port.to_s - end - sleep(0.5) - end - - - def fake_reports_success - _,status = Process.waitpid2( @fake_pid ) - @fake_pid = nil - status.success? - end - - -end # class Environment - - -class NBDScenarios < Test::Unit::TestCase - def setup - @env = Environment.new - end - - def teardown - @env.nbd1.can_die(0) - @env.nbd2.can_die(0) - @env.cleanup - end - - - def test_read1 - @env.writefile1("f"*64) - @env.serve1 - - [0, 12, 63].each do |num| - - assert_equal( - @env.nbd1.read(num*@env.blocksize, @env.blocksize), - @env.file1.read(num*@env.blocksize, @env.blocksize) - ) - end - - [124, 1200, 10028, 25488].each do |num| - assert_equal(@env.nbd1.read(num, 4), @env.file1.read(num, 4)) - end - end - - # Check that we're not - # - def test_writeread1 - @env.writefile1("0"*64) - @env.serve1 - - [0, 12, 63].each do |num| - data = "X"*@env.blocksize - @env.nbd1.write(num*@env.blocksize, data) - assert_equal(data, @env.file1.read(num*@env.blocksize, data.size)) - assert_equal(data, @env.nbd1.read(num*@env.blocksize, data.size)) - end - end - - # Check that we're not overstepping or understepping where our writes end - # up. - # - def test_writeread2 - @env.writefile1("0"*1024) - @env.serve1 - - d0 = "\0"*@env.blocksize - d1 = "X"*@env.blocksize - (0..63).each do |num| - @env.nbd1.write(num*@env.blocksize*2, d1) - end - (0..63).each do |num| - assert_equal(d0, @env.nbd1.read(((2*num)+1)*@env.blocksize, d0.size)) - end - end - - - def test_mirror - @env.writefile1( "f"*4 ) - @env.serve1 - - @env.writefile2( "0"*4 ) - @env.listen2 - - @env.nbd1.can_die - stdout, stderr = @env.mirror12 - - @env.nbd1.join - - assert_equal(@env.file1.read_original( 0, @env.blocksize ), - @env.file2.read( 0, @env.blocksize ) ) - assert @env.status2['has_control'], "destination didn't take control" - end - +test_files = Dir[File.dirname( __FILE__ ) + "/test*.rb"] +for filename in test_files + require filename end - - -class NBDConnectSourceFailureScenarios < Test::Unit::TestCase - def setup - @env = Environment.new - @env.writefile1( "f" * 4 ) - @env.serve1 - - end - - def teardown - @env.nbd1.can_die(0) - @env.cleanup - end - - - def test_failure_to_connect_reported_in_mirror_cmd_response - stdout, stderr = @env.mirror12_unchecked - assert_match( /failed to connect/, stderr ) - end - - - def test_destination_hangs_after_connect_reports_error_at_source - run_fake( "dest/hang_after_connect" ) - - stdout, stderr = @env.mirror12_unchecked - assert_match( /Remote server failed to respond/, stderr ) - assert_success - end - - - def test_destination_rejects_connection_reports_error_at_source - run_fake( "dest/reject_acl" ) - - stdout, stderr = @env.mirror12_unchecked - assert_match /Mirror was rejected/, stderr - assert_success - end - - def test_wrong_size_causes_disconnect - run_fake( "dest/hello_wrong_size" ) - stdout, stderr = @env.mirror12_unchecked - assert_match /Remote size does not match local size/, stderr - assert_success - end - - - def test_wrong_magic_causes_disconnect - run_fake( "dest/hello_wrong_magic" ) - stdout, stderr = @env.mirror12_unchecked - assert_match /Mirror was rejected/, stderr - assert_success "dest/hello_wrong_magic fake failed" - end - - - def test_disconnect_after_hello_causes_retry - run_fake( "dest/close_after_hello" ) - stdout, stderr = @env.mirror12_unchecked - assert_match( /Mirror started/, stdout ) - - assert_success - end - - - def test_write_times_out_causes_retry - run_fake( "dest/hang_after_write" ) - stdout, stderr = @env.mirror12_unchecked - - assert_success - end - - - def test_rejected_write_causes_retry - run_fake( "dest/error_on_write" ) - stdout, stderr = @env.mirror12_unchecked - assert_success - end - - - private - def run_fake(name) - @env.run_fake( name, @env.ip, @env.port2 ) - end - - def assert_success( msg=nil ) - assert @env.fake_reports_success, msg || "Fake failed" - end - - -end - - -class NBDConnectDestFailureScenarios < Test::Unit::TestCase - - def setup - @env = Environment.new - @env.writefile1( "0" * 4 ) - @env.listen1 - end - - def teardown - @env.cleanup - end - - - def test_hello_blocked_by_disconnect_causes_error_not_fatal - run_fake( "source/close_after_connect" ) - assert_no_control - end - - - def test_hello_goes_astray_causes_timeout_error - run_fake( "source/hang_after_hello" ) - assert_no_control - end - - - def test_disconnect_after_hello_causes_error_not_fatal - run_fake( "source/close_after_hello" ) - assert_no_control - end - - - def test_double_connect_during_hello - run_fake( "source/connect_during_hello" ) - end - - - def test_acl_rejection - @env.acl1("127.0.0.1") - run_fake( "source/connect_from_banned_ip") - end - - - def test_bad_write - run_fake( "source/write_out_of_range" ) - end - - - private - def run_fake( name ) - @env.run_fake( name, @env.ip, @env.port1 ) - assert @env.fake_reports_success, "#{name} failed." - end - - def assert_no_control - status, stderr = @env.status1 - assert !status['has_control'], "Thought it had control" - end - - -end # class NBDConnectDestFailureScenarios diff --git a/tests/acceptance/test_dest_error_handling.rb b/tests/acceptance/test_dest_error_handling.rb new file mode 100644 index 0000000..38980ef --- /dev/null +++ b/tests/acceptance/test_dest_error_handling.rb @@ -0,0 +1,65 @@ +# encoding: utf-8 + +require 'test/unit' +require 'environment' + +class NBDConnectDestFailureScenarios < Test::Unit::TestCase + + def setup + @env = Environment.new + @env.writefile1( "0" * 4 ) + @env.listen1 + end + + def teardown + @env.cleanup + end + + + def test_hello_blocked_by_disconnect_causes_error_not_fatal + run_fake( "source/close_after_connect" ) + assert_no_control + end + + + def test_hello_goes_astray_causes_timeout_error + run_fake( "source/hang_after_hello" ) + assert_no_control + end + + + def test_disconnect_after_hello_causes_error_not_fatal + run_fake( "source/close_after_hello" ) + assert_no_control + end + + + def test_double_connect_during_hello + run_fake( "source/connect_during_hello" ) + end + + + def test_acl_rejection + @env.acl1("127.0.0.1") + run_fake( "source/connect_from_banned_ip") + end + + + def test_bad_write + run_fake( "source/write_out_of_range" ) + end + + + private + def run_fake( name ) + @env.run_fake( name, @env.ip, @env.port1 ) + assert @env.fake_reports_success, "#{name} failed." + end + + def assert_no_control + status, stderr = @env.status1 + assert !status['has_control'], "Thought it had control" + end + + +end # class NBDConnectDestFailureScenarios diff --git a/tests/acceptance/test_happy_path.rb b/tests/acceptance/test_happy_path.rb new file mode 100644 index 0000000..8f0c2c7 --- /dev/null +++ b/tests/acceptance/test_happy_path.rb @@ -0,0 +1,84 @@ +# encoding: utf-8 + +require 'test/unit' +require 'environment' + +class TestHappyPath < Test::Unit::TestCase + def setup + @env = Environment.new + end + + def teardown + @env.nbd1.can_die(0) + @env.nbd2.can_die(0) + @env.cleanup + end + + + def test_read1 + @env.writefile1("f"*64) + @env.serve1 + + [0, 12, 63].each do |num| + + assert_equal( + @env.nbd1.read(num*@env.blocksize, @env.blocksize), + @env.file1.read(num*@env.blocksize, @env.blocksize) + ) + end + + [124, 1200, 10028, 25488].each do |num| + assert_equal(@env.nbd1.read(num, 4), @env.file1.read(num, 4)) + end + end + + # Check that we're not + # + def test_writeread1 + @env.writefile1("0"*64) + @env.serve1 + + [0, 12, 63].each do |num| + data = "X"*@env.blocksize + @env.nbd1.write(num*@env.blocksize, data) + assert_equal(data, @env.file1.read(num*@env.blocksize, data.size)) + assert_equal(data, @env.nbd1.read(num*@env.blocksize, data.size)) + end + end + + # Check that we're not overstepping or understepping where our writes end + # up. + # + def test_writeread2 + @env.writefile1("0"*1024) + @env.serve1 + + d0 = "\0"*@env.blocksize + d1 = "X"*@env.blocksize + (0..63).each do |num| + @env.nbd1.write(num*@env.blocksize*2, d1) + end + (0..63).each do |num| + assert_equal(d0, @env.nbd1.read(((2*num)+1)*@env.blocksize, d0.size)) + end + end + + + def test_mirror + @env.writefile1( "f"*4 ) + @env.serve1 + + @env.writefile2( "0"*4 ) + @env.listen2 + + @env.nbd1.can_die + stdout, stderr = @env.mirror12 + + @env.nbd1.join + + assert_equal(@env.file1.read_original( 0, @env.blocksize ), + @env.file2.read( 0, @env.blocksize ) ) + assert @env.status2['has_control'], "destination didn't take control" + end + +end diff --git a/tests/acceptance/test_source_error_handling.rb b/tests/acceptance/test_source_error_handling.rb new file mode 100644 index 0000000..8231e34 --- /dev/null +++ b/tests/acceptance/test_source_error_handling.rb @@ -0,0 +1,95 @@ +# encoding: utf-8 + +require 'test/unit' +require 'environment' + + +class TestSourceErrorHandling < Test::Unit::TestCase + + def setup + @env = Environment.new + @env.writefile1( "f" * 4 ) + @env.serve1 + end + + + def teardown + @env.nbd1.can_die(0) + @env.cleanup + end + + + def test_failure_to_connect_reported_in_mirror_cmd_response + stdout, stderr = @env.mirror12_unchecked + assert_match( /failed to connect/, stderr ) + end + + + def test_destination_hangs_after_connect_reports_error_at_source + run_fake( "dest/hang_after_connect" ) + + stdout, stderr = @env.mirror12_unchecked + assert_match( /Remote server failed to respond/, stderr ) + assert_success + end + + + def test_destination_rejects_connection_reports_error_at_source + run_fake( "dest/reject_acl" ) + + stdout, stderr = @env.mirror12_unchecked + assert_match /Mirror was rejected/, stderr + assert_success + end + + def test_wrong_size_causes_disconnect + run_fake( "dest/hello_wrong_size" ) + stdout, stderr = @env.mirror12_unchecked + assert_match /Remote size does not match local size/, stderr + assert_success + end + + + def test_wrong_magic_causes_disconnect + run_fake( "dest/hello_wrong_magic" ) + stdout, stderr = @env.mirror12_unchecked + assert_match /Mirror was rejected/, stderr + assert_success "dest/hello_wrong_magic fake failed" + end + + + def test_disconnect_after_hello_causes_retry + run_fake( "dest/close_after_hello" ) + stdout, stderr = @env.mirror12_unchecked + assert_match( /Mirror started/, stdout ) + + assert_success + end + + + def test_write_times_out_causes_retry + run_fake( "dest/hang_after_write" ) + stdout, stderr = @env.mirror12_unchecked + + assert_success + end + + + def test_rejected_write_causes_retry + run_fake( "dest/error_on_write" ) + stdout, stderr = @env.mirror12_unchecked + assert_success + end + + + private + def run_fake(name) + @env.run_fake( name, @env.ip, @env.port2 ) + end + + def assert_success( msg=nil ) + assert @env.fake_reports_success, msg || "Fake failed" + end + + +end