diff --git a/core/io/copy_stream_spec.rb b/core/io/copy_stream_spec.rb index dd8b201dc8..9148770c1e 100644 --- a/core/io/copy_stream_spec.rb +++ b/core/io/copy_stream_spec.rb @@ -1,50 +1,184 @@ require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../fixtures/classes', __FILE__) ruby_version_is "1.9" do - describe "IO.copy_stream" do - it "needs to be reviewed for spec completeness" + describe :io_copy_stream_to_file, :shared => true do + it "copies the entire IO contents to the file" do + IO.copy_stream(@object.from, @to_name) + @to_name.should have_data(@content) + end + + it "returns the number of bytes copied" do + IO.copy_stream(@object.from, @to_name).should == @size + end + + it "copies only length bytes when specified" do + IO.copy_stream(@object.from, @to_name, 8).should == 8 + @to_name.should have_data("Line one") + end + + it "copies only length bytes from the offset" do + IO.copy_stream(@object.from, @to_name, 8, 4).should == 8 + @to_name.should have_data(" one\n\nLi") + end + + it "calls #to_path to convert on object to a file name" do + obj = mock("io_copy_stream_to") + obj.should_receive(:to_path).and_return(@to_name) + + IO.copy_stream(@object.from, obj) + @to_name.should have_data(@content) + end + + it "raises a TypeError if #to_path does not return a String" do + obj = mock("io_copy_stream_to") + obj.should_receive(:to_path).and_return(1) + + lambda { IO.copy_stream(@object.from, obj) }.should raise_error(TypeError) + end end - describe "IO.copy_stream" do - before :each do - # TODO: multi-line content (esp. on Windows!) - @content = "one-two-three" + describe :io_copy_stream_to_io, :shared => true do + it "copies the entire IO contents to the IO" do + IO.copy_stream(@object.from, @to_io) + @to_name.should have_data(@content) + end - @filename_src = tmp("source") - touch(@filename_src) { |f| f.write(@content) } - @io_src = new_io(@filename_src, "rb") + it "returns the number of bytes copied" do + IO.copy_stream(@object.from, @to_io).should == @size + end - @filename_dest = tmp("destination") - # TODO: new_io doesn't work here due to bug in mspsec - # @io_dest = new_io(@filename_dest, "w") - @io_dest = File.open(@filename_dest, "wb") + it "starts writing at the destination IO's current position" do + @to_io.write("prelude ") + IO.copy_stream(@object.from, @to_io) + @to_name.should have_data("prelude " + @content) end - after :each do - @io_src.close unless @io_src.closed? - @io_dest.close unless @io_dest.closed? - File.unlink @filename_src - File.unlink @filename_dest + it "leaves the destination IO position at the last write" do + IO.copy_stream(@object.from, @to_io) + @to_io.pos.should == @size end - it "copies from an IO instance to a named file" do - IO.copy_stream(@io_src, @filename_dest).should == @content.size - File.read(@filename_dest).should == @content + it "raises an IOError if the destination IO is not open for writing" do + @to_io.close + @to_io = File.open @to_name, "r" + lambda { IO.copy_stream @object.from, @to_io }.should raise_error(IOError) end - it "copies from a named file to a named file" do - IO.copy_stream(@filename_src, @filename_dest).should == @content.size - File.read(@filename_dest).should == @content + it "does not close the destination IO" do + IO.copy_stream(@object.from, @to_io) + @to_io.closed?.should be_false end - it "copies from a named file to an IO instance" do - IO.copy_stream(@filename_src, @io_dest).should == @content.size - File.read(@filename_dest).should == @content + it "copies only length bytes when specified" do + IO.copy_stream(@object.from, @to_io, 8).should == 8 + @to_name.should have_data("Line one") end - it "copies from an IO instance to an IO instance" do - IO.copy_stream(@io_src, @io_dest).should == @content.size - File.read(@filename_dest).should == @content + it "copies only length bytes from the offset" do + IO.copy_stream(@object.from, @to_io, 8, 4).should == 8 + @to_name.should have_data(" one\n\nLi") + end + end + + describe "IO.copy_stream" do + before :each do + @from_name = fixture __FILE__, "copy_stream.txt" + @to_name = tmp("io_copy_stream_io_name") + + @content = IO.read(@from_name) + @size = @content.size + end + + after :each do + rm_r @to_name + end + + describe "from an IO" do + before :each do + @from_io = File.open @from_name, "rb" + IOSpecs::CopyStream.from = @from_io + end + + after :each do + @from_io.close + end + + it "raises an IOError if the source IO is not open for reading" do + @from_io.close + @from_io = File.open @from_name, "a" + lambda { IO.copy_stream @from_io, @to_name }.should raise_error(IOError) + end + + it "does not close the source IO" do + IO.copy_stream(@from_io, @to_name) + @from_io.closed?.should be_false + end + + it "does not change the IO offset when an offset is specified" do + @from_io.pos = 10 + IO.copy_stream(@from_io, @to_name, 8, 4) + @from_io.pos.should == 10 + end + + it "does change the IO offset when an offset is not specified" do + @from_io.pos = 10 + IO.copy_stream(@from_io, @to_name) + @from_io.pos.should == 42 + end + + describe "to a file name" do + it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream + end + + describe "to an IO" do + before :each do + @to_io = File.open @to_name, "wb" + end + + after :each do + @to_io.close + end + + it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream + end + end + + describe "from a file name" do + before :each do + IOSpecs::CopyStream.from = @from_name + end + + it "calls #to_path to convert on object to a file name" do + obj = mock("io_copy_stream_from") + obj.should_receive(:to_path).and_return(@from_name) + + IO.copy_stream(obj, @to_name) + @to_name.should have_data(@content) + end + + it "raises a TypeError if #to_path does not return a String" do + obj = mock("io_copy_stream_from") + obj.should_receive(:to_path).and_return(1) + + lambda { IO.copy_stream(obj, @to_name) }.should raise_error(TypeError) + end + + describe "to a file name" do + it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream + end + + describe "to an IO" do + before :each do + @to_io = File.open @to_name, "wb" + end + + after :each do + @to_io.close + end + + it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream + end end end end diff --git a/core/io/fixtures/classes.rb b/core/io/fixtures/classes.rb index 5ff334bdf9..6ac70b5c3e 100644 --- a/core/io/fixtures/classes.rb +++ b/core/io/fixtures/classes.rb @@ -74,4 +74,14 @@ def self.closed_io def self.io_mock(obj, method, &block) obj.singleton_class.send(:define_method, method, &block) end + + module CopyStream + def self.from=(from) + @from = from + end + + def self.from + @from + end + end end diff --git a/core/io/fixtures/copy_stream.txt b/core/io/fixtures/copy_stream.txt new file mode 100644 index 0000000000..a2d827b351 --- /dev/null +++ b/core/io/fixtures/copy_stream.txt @@ -0,0 +1,6 @@ +Line one + +Line three +Line four + +Line last