diff options
Diffstat (limited to 'spec/ruby/library')
1690 files changed, 60121 insertions, 0 deletions
diff --git a/spec/ruby/library/English/English_spec.rb b/spec/ruby/library/English/English_spec.rb new file mode 100644 index 0000000000..4d615d1e25 --- /dev/null +++ b/spec/ruby/library/English/English_spec.rb @@ -0,0 +1,173 @@ +require_relative '../../spec_helper' + +require 'English' + +describe "English" do + it "aliases $ERROR_INFO to $!" do + begin + raise "error" + rescue + $ERROR_INFO.should_not be_nil + $ERROR_INFO.should == $! + end + $ERROR_INFO.should be_nil + end + + it "aliases $ERROR_POSITION to $@" do + begin + raise "error" + rescue + $ERROR_POSITION.should_not be_nil + $ERROR_POSITION.should == $@ + end + $ERROR_POSITION.should be_nil + end + + it "aliases $FS to $;" do + original = $; + suppress_warning {$; = ","} + $FS.should_not be_nil + $FS.should == $; + suppress_warning {$; = original} + end + + it "aliases $FIELD_SEPARATOR to $;" do + original = $; + suppress_warning {$; = ","} + $FIELD_SEPARATOR.should_not be_nil + $FIELD_SEPARATOR.should == $; + suppress_warning {$; = original} + end + + it "aliases $OFS to $," do + original = $, + suppress_warning {$, = "|"} + $OFS.should_not be_nil + $OFS.should == $, + suppress_warning {$, = original} + end + + it "aliases $OUTPUT_FIELD_SEPARATOR to $," do + original = $, + suppress_warning {$, = "|"} + $OUTPUT_FIELD_SEPARATOR.should_not be_nil + $OUTPUT_FIELD_SEPARATOR.should == $, + suppress_warning {$, = original} + end + + it "aliases $RS to $/" do + $RS.should_not be_nil + $RS.should == $/ + end + + it "aliases $INPUT_RECORD_SEPARATOR to $/" do + $INPUT_RECORD_SEPARATOR.should_not be_nil + $INPUT_RECORD_SEPARATOR.should == $/ + end + + it "aliases $ORS to $\\" do + original = $\ + suppress_warning {$\ = "\t"} + $ORS.should_not be_nil + $ORS.should == $\ + suppress_warning {$\ = original} + end + + it "aliases $OUTPUT_RECORD_SEPARATOR to $\\" do + original = $\ + suppress_warning {$\ = "\t"} + $OUTPUT_RECORD_SEPARATOR.should_not be_nil + $OUTPUT_RECORD_SEPARATOR.should == $\ + suppress_warning {$\ = original} + end + + it "aliases $INPUT_LINE_NUMBER to $." do + $INPUT_LINE_NUMBER.should_not be_nil + $INPUT_LINE_NUMBER.should == $. + end + + it "aliases $NR to $." do + $NR.should_not be_nil + $NR.should == $. + end + + it "aliases $LAST_READ_LINE to $_ needs to be reviewed for spec completeness" + + it "aliases $DEFAULT_OUTPUT to $>" do + $DEFAULT_OUTPUT.should_not be_nil + $DEFAULT_OUTPUT.should == $> + end + + it "aliases $DEFAULT_INPUT to $<" do + $DEFAULT_INPUT.should_not be_nil + $DEFAULT_INPUT.should == $< + end + + it "aliases $PID to $$" do + $PID.should_not be_nil + $PID.should == $$ + end + + it "aliases $PID to $$" do + $PID.should_not be_nil + $PID.should == $$ + end + + it "aliases $PROCESS_ID to $$" do + $PROCESS_ID.should_not be_nil + $PROCESS_ID.should == $$ + end + + it "aliases $CHILD_STATUS to $?" do + ruby_exe('exit 0') + $CHILD_STATUS.should_not be_nil + $CHILD_STATUS.should == $? + end + + it "aliases $LAST_MATCH_INFO to $~" do + /c(a)t/ =~ "cat" + $LAST_MATCH_INFO.should_not be_nil + $LAST_MATCH_INFO.should == $~ + end + + ruby_version_is ""..."3.3" do + it "aliases $IGNORECASE to $=" do + $VERBOSE, verbose = nil, $VERBOSE + begin + $IGNORECASE.should_not be_nil + $IGNORECASE.should == $= + ensure + $VERBOSE = verbose + end + end + end + + it "aliases $ARGV to $*" do + $ARGV.should_not be_nil + $ARGV.should == $* + end + + it "aliases $MATCH to $&" do + /c(a)t/ =~ "cat" + $MATCH.should_not be_nil + $MATCH.should == $& + end + + it "aliases $PREMATCH to $`" do + /c(a)t/ =~ "cat" + $PREMATCH.should_not be_nil + $PREMATCH.should == $` + end + + it "aliases $POSTMATCH to $'" do + /c(a)t/ =~ "cat" + $POSTMATCH.should_not be_nil + $POSTMATCH.should == $' + end + + it "aliases $LAST_PAREN_MATCH to $+" do + /c(a)t/ =~ "cat" + $LAST_PAREN_MATCH.should_not be_nil + $LAST_PAREN_MATCH.should == $+ + end +end diff --git a/spec/ruby/library/English/alias_spec.rb b/spec/ruby/library/English/alias_spec.rb new file mode 100644 index 0000000000..78ccfb4398 --- /dev/null +++ b/spec/ruby/library/English/alias_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'English' + +describe "English" do + it "aliases $! to $ERROR_INFO and $ERROR_INFO still returns an Exception with a backtrace" do + exception = (1 / 0 rescue $ERROR_INFO) + exception.should be_kind_of(Exception) + exception.backtrace.should be_kind_of(Array) + end + + it "aliases $@ to $ERROR_POSITION and $ERROR_POSITION still returns a backtrace" do + (1 / 0 rescue $ERROR_POSITION).should be_kind_of(Array) + end +end diff --git a/spec/ruby/library/abbrev/abbrev_spec.rb b/spec/ruby/library/abbrev/abbrev_spec.rb new file mode 100644 index 0000000000..61b0926597 --- /dev/null +++ b/spec/ruby/library/abbrev/abbrev_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require 'abbrev' + +#test both Abbrev.abbrev and Array#abbrev in +#the same manner, as they're more or less aliases +#of one another + +[["Abbrev.abbrev", -> a { Abbrev.abbrev(a)}], + ["Array#abbrev", -> a { a.abbrev}] +].each do |(name, func)| + + describe name do + it "returns a hash of all unambiguous abbreviations of the array of strings passed in" do + func.call(['ruby', 'rules']).should == {"rub" => "ruby", + "ruby" => "ruby", + "rul" => "rules", + "rule" => "rules", + "rules" => "rules"} + + func.call(["car", "cone"]).should == {"ca" => "car", + "car" => "car", + "co" => "cone", + "con" => "cone", + "cone" => "cone"} + end + + it "returns an empty hash when called on an empty array" do + func.call([]).should == {} + end + end +end diff --git a/spec/ruby/library/base64/decode64_spec.rb b/spec/ruby/library/base64/decode64_spec.rb new file mode 100644 index 0000000000..6dd33dddfe --- /dev/null +++ b/spec/ruby/library/base64/decode64_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' + +require 'base64' + +describe "Base64#decode64" do + it "returns the Base64-decoded version of the given string" do + Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n").should == "Send reinforcements" + end + + it "returns the Base64-decoded version of the given shared string" do + Base64.decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==\n".split(" ").last).should == "Send reinforcements" + end + + it "returns the Base64-decoded version of the given string with wrong padding" do + Base64.decode64("XU2VuZCByZWluZm9yY2VtZW50cw===").should == "]M\x95\xB9\x90\x81\xC9\x95\xA5\xB9\x99\xBD\xC9\x8D\x95\xB5\x95\xB9\xD1\xCC".b + end + + it "returns the Base64-decoded version of the given string that contains an invalid character" do + Base64.decode64("%3D").should == "\xDC".b + end + + it "returns a binary encoded string" do + Base64.decode64("SEk=").encoding.should == Encoding::BINARY + end + + it "decodes without padding suffix ==" do + Base64.decode64("eyJrZXkiOnsibiI6InR0dCJ9fQ").should == "{\"key\":{\"n\":\"ttt\"}}" + end +end diff --git a/spec/ruby/library/base64/encode64_spec.rb b/spec/ruby/library/base64/encode64_spec.rb new file mode 100644 index 0000000000..64de6257bc --- /dev/null +++ b/spec/ruby/library/base64/encode64_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' + +require 'base64' + +describe "Base64#encode64" do + it "returns the Base64-encoded version of the given string" do + Base64.encode64("Now is the time for all good coders\nto learn Ruby").should == + "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n" + end + + it "returns the Base64-encoded version of the given string" do + Base64.encode64('Send reinforcements').should == "U2VuZCByZWluZm9yY2VtZW50cw==\n" + end + + it "returns the Base64-encoded version of the given shared string" do + Base64.encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should == + "dG8gbGVhcm4gUnVieQ==\n" + end + + it "returns a US_ASCII encoded string" do + Base64.encode64("HI").encoding.should == Encoding::US_ASCII + end +end diff --git a/spec/ruby/library/base64/strict_decode64_spec.rb b/spec/ruby/library/base64/strict_decode64_spec.rb new file mode 100644 index 0000000000..d258223c82 --- /dev/null +++ b/spec/ruby/library/base64/strict_decode64_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' + +require 'base64' + +describe "Base64#strict_decode64" do + it "returns the Base64-decoded version of the given string" do + Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==").should == "Send reinforcements" + end + + it "returns the Base64-decoded version of the given shared string" do + Base64.strict_decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==".split(" ").last).should == "Send reinforcements" + end + + it "raises ArgumentError when the given string contains CR" do + -> do + Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\r") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the given string contains LF" do + -> do + Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the given string has wrong padding" do + -> do + Base64.strict_decode64("=U2VuZCByZWluZm9yY2VtZW50cw==") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the given string contains an invalid character" do + -> do + Base64.strict_decode64("%3D") + end.should raise_error(ArgumentError) + end + + it "returns a binary encoded string" do + Base64.strict_decode64("SEk=").encoding.should == Encoding::BINARY + end +end diff --git a/spec/ruby/library/base64/strict_encode64_spec.rb b/spec/ruby/library/base64/strict_encode64_spec.rb new file mode 100644 index 0000000000..7cabcf190c --- /dev/null +++ b/spec/ruby/library/base64/strict_encode64_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' + +require 'base64' + +describe "Base64#strict_encode64" do + it "returns the Base64-encoded version of the given string" do + Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby").should == + "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ==" + end + + it "returns the Base64-encoded version of the given shared string" do + Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should == + "dG8gbGVhcm4gUnVieQ==" + end + + it "returns a US_ASCII encoded string" do + Base64.strict_encode64("HI").encoding.should == Encoding::US_ASCII + end +end diff --git a/spec/ruby/library/base64/urlsafe_decode64_spec.rb b/spec/ruby/library/base64/urlsafe_decode64_spec.rb new file mode 100644 index 0000000000..1b813ee1b9 --- /dev/null +++ b/spec/ruby/library/base64/urlsafe_decode64_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' + +require 'base64' + +describe "Base64#urlsafe_decode64" do + it "uses '_' instead of '/'" do + decoded = Base64.urlsafe_decode64("V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_") + decoded.should == 'Where am I? Who am I? Am I? I?' + end + + it "uses '-' instead of '+'" do + decoded = Base64.urlsafe_decode64('IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-') + decoded.should == '"Being disintegrated makes me ve-ry an-gry!" <huff, huff>' + end + + it "does not require padding" do + Base64.urlsafe_decode64("MQ").should == "1" + end +end diff --git a/spec/ruby/library/base64/urlsafe_encode64_spec.rb b/spec/ruby/library/base64/urlsafe_encode64_spec.rb new file mode 100644 index 0000000000..de1f235cea --- /dev/null +++ b/spec/ruby/library/base64/urlsafe_encode64_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' + +require 'base64' + +describe "Base64#urlsafe_encode64" do + it "uses '_' instead of '/'" do + encoded = Base64.urlsafe_encode64('Where am I? Who am I? Am I? I?') + encoded.should == "V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_" + end + + it "uses '-' instead of '+'" do + encoded = Base64.urlsafe_encode64('"Being disintegrated makes me ve-ry an-gry!" <huff, huff>') + encoded.should == 'IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-' + end + + it "makes padding optional" do + Base64.urlsafe_encode64("1", padding: false).should == "MQ" + Base64.urlsafe_encode64("1").should == "MQ==" + end +end diff --git a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb new file mode 100644 index 0000000000..8596356abd --- /dev/null +++ b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb @@ -0,0 +1,239 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal" do + it "is not defined unless it is required" do + ruby_exe('puts Object.const_defined?(:BigDecimal)').should == "false\n" + end +end + +describe "Kernel#BigDecimal" do + + it "creates a new object of class BigDecimal" do + BigDecimal("3.14159").should be_kind_of(BigDecimal) + (0..9).each {|i| + BigDecimal("1#{i}").should == 10 + i + BigDecimal("-1#{i}").should == -10 - i + BigDecimal("1E#{i}").should == 10**i + BigDecimal("1000000E-#{i}").should == 10**(6-i).to_f + # ^ to_f to avoid Rational type + } + (1..9).each {|i| + BigDecimal("100.#{i}").to_s.should =~ /\A0\.100#{i}E3\z/i + BigDecimal("-100.#{i}").to_s.should =~ /\A-0\.100#{i}E3\z/i + } + end + + it "BigDecimal(Rational) with bigger-than-double numerator" do + rational = 99999999999999999999/100r + rational.numerator.should > 2**64 + BigDecimal(rational, 100).to_s.should == "0.99999999999999999999e18" + end + + it "accepts significant digits >= given precision" do + BigDecimal("3.1415923", 10).should == BigDecimal("3.1415923") + end + + it "determines precision from initial value" do + pi_string = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" + BigDecimal(pi_string).precision.should == pi_string.size-1 + end + + it "ignores leading and trailing whitespace" do + BigDecimal(" \t\n \r1234\t\r\n ").should == BigDecimal("1234") + BigDecimal(" \t\n \rNaN \n").should.nan? + BigDecimal(" \t\n \rInfinity \n").infinite?.should == 1 + BigDecimal(" \t\n \r-Infinity \n").infinite?.should == -1 + end + + it "coerces the value argument with #to_str" do + initial = mock("value") + initial.should_receive(:to_str).and_return("123") + BigDecimal(initial).should == BigDecimal("123") + end + + it "does not ignores trailing garbage" do + -> { BigDecimal("123E45ruby") }.should raise_error(ArgumentError) + -> { BigDecimal("123x45") }.should raise_error(ArgumentError) + -> { BigDecimal("123.4%E5") }.should raise_error(ArgumentError) + -> { BigDecimal("1E2E3E4E5E") }.should raise_error(ArgumentError) + end + + it "raises ArgumentError for invalid strings" do + -> { BigDecimal("ruby") }.should raise_error(ArgumentError) + -> { BigDecimal(" \t\n \r-\t\t\tInfinity \n") }.should raise_error(ArgumentError) + end + + it "allows omitting the integer part" do + BigDecimal(".123").should == BigDecimal("0.123") + end + + it "process underscores as Float()" do + reference = BigDecimal("12345.67E89") + + BigDecimal("12_345.67E89").should == reference + -> { BigDecimal("1_2_3_4_5_._6____7_E89") }.should raise_error(ArgumentError) + -> { BigDecimal("12345_.67E_8__9_") }.should raise_error(ArgumentError) + end + + it "accepts NaN and [+-]Infinity" do + BigDecimal("NaN").should.nan? + + pos_inf = BigDecimal("Infinity") + pos_inf.should_not.finite? + pos_inf.should > 0 + pos_inf.should == BigDecimal("+Infinity") + + neg_inf = BigDecimal("-Infinity") + neg_inf.should_not.finite? + neg_inf.should < 0 + end + + describe "with exception: false" do + it "returns nil for invalid strings" do + BigDecimal("invalid", exception: false).should be_nil + BigDecimal("0invalid", exception: false).should be_nil + BigDecimal("invalid0", exception: false).should be_nil + if BigDecimal::VERSION >= "3.1.9" + BigDecimal("0.", exception: false).to_i.should == 0 + else + BigDecimal("0.", exception: false).should be_nil + end + end + end + + describe "accepts NaN and [+-]Infinity as Float values" do + it "works without an explicit precision" do + BigDecimal(Float::NAN).should.nan? + + pos_inf = BigDecimal(Float::INFINITY) + pos_inf.should_not.finite? + pos_inf.should > 0 + pos_inf.should == BigDecimal("+Infinity") + + neg_inf = BigDecimal(-Float::INFINITY) + neg_inf.should_not.finite? + neg_inf.should < 0 + end + + it "works with an explicit precision" do + BigDecimal(Float::NAN, Float::DIG).should.nan? + + pos_inf = BigDecimal(Float::INFINITY, Float::DIG) + pos_inf.should_not.finite? + pos_inf.should > 0 + pos_inf.should == BigDecimal("+Infinity") + + neg_inf = BigDecimal(-Float::INFINITY, Float::DIG) + neg_inf.should_not.finite? + neg_inf.should < 0 + end + end + + it "allows for [eEdD] as exponent separator" do + reference = BigDecimal("12345.67E89") + + BigDecimal("12345.67e89").should == reference + BigDecimal("12345.67E89").should == reference + BigDecimal("12345.67d89").should == reference + BigDecimal("12345.67D89").should == reference + end + + it "allows for varying signs" do + reference = BigDecimal("123.456E1") + + BigDecimal("+123.456E1").should == reference + BigDecimal("-123.456E1").should == -reference + BigDecimal("123.456E+1").should == reference + BigDecimal("12345.6E-1").should == reference + BigDecimal("+123.456E+1").should == reference + BigDecimal("+12345.6E-1").should == reference + BigDecimal("-123.456E+1").should == -reference + BigDecimal("-12345.6E-1").should == -reference + end + + version_is BigDecimal::VERSION, "3.3.0" do + it "allows Float without precision" do + BigDecimal(1.2).should == BigDecimal("1.2") + end + end + + it "returns appropriate BigDecimal zero for signed zero" do + BigDecimal(-0.0, Float::DIG).sign.should == -1 + BigDecimal(0.0, Float::DIG).sign.should == 1 + end + + it "pre-coerces long integers" do + BigDecimal(3).add(1 << 50, 3).should == BigDecimal('0.113e16') + end + + it "does not call to_s when calling inspect" do + value = BigDecimal('44.44') + value.to_s.should == '0.4444e2' + value.inspect.should == '0.4444e2' + + ruby_exe( <<-'EOF').should == "cheese 0.4444e2" + require 'bigdecimal' + module BigDecimalOverride + def to_s; "cheese"; end + end + BigDecimal.prepend BigDecimalOverride + value = BigDecimal('44.44') + print "#{value.to_s} #{value.inspect}" + EOF + end + + describe "when interacting with Rational" do + before :each do + @a = BigDecimal('166.666666666') + @b = Rational(500, 3) + @c = @a - @b + end + + # Check the input is as we understand it + + it "has the LHS print as expected" do + @a.to_s.should == "0.166666666666e3" + @a.to_f.to_s.should == "166.666666666" + Float(@a).to_s.should == "166.666666666" + end + + it "has the RHS print as expected" do + @b.to_s.should == "500/3" + @b.to_f.to_s.should == "166.66666666666666" + Float(@b).to_s.should == "166.66666666666666" + end + + it "produces the expected result when done via Float" do + (Float(@a) - Float(@b)).to_s.should == "-6.666596163995564e-10" + end + + it "produces the expected result when done via to_f" do + (@a.to_f - @b.to_f).to_s.should == "-6.666596163995564e-10" + end + + # Check underlying methods work as we understand + + it "BigDecimal(Rational, 18) produces the result we expect" do + BigDecimal(@b, 18).to_s.should == "0.166666666666666667e3" + end + + # Check the top-level expression works as we expect + + it "produces a BigDecimal" do + @c.class.should == BigDecimal + end + + it "produces the expected result" do + @c.round(15).should == BigDecimal("-0.666667e-9") + @c.round(15).to_s.should == "-0.666667e-9" + end + + it "produces the correct class for other arithmetic operators" do + (@a + @b).class.should == BigDecimal + (@a * @b).class.should == BigDecimal + (@a / @b).class.should == BigDecimal + (@a % @b).class.should == BigDecimal + end + end +end diff --git a/spec/ruby/library/bigdecimal/abs_spec.rb b/spec/ruby/library/bigdecimal/abs_spec.rb new file mode 100644 index 0000000000..95dc45a905 --- /dev/null +++ b/spec/ruby/library/bigdecimal/abs_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#abs" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @mixed = BigDecimal("1.23456789") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns the absolute value" do + pos_int = BigDecimal("2E5555") + neg_int = BigDecimal("-2E5555") + pos_frac = BigDecimal("2E-9999") + neg_frac = BigDecimal("-2E-9999") + + pos_int.abs.should == pos_int + neg_int.abs.should == pos_int + pos_frac.abs.should == pos_frac + neg_frac.abs.should == pos_frac + @one.abs.should == 1 + @two.abs.should == 2 + @three.abs.should == 3 + @mixed.abs.should == @mixed + @one_minus.abs.should == @one + end + + it "properly handles special values" do + @infinity.abs.should == @infinity + @infinity_minus.abs.should == @infinity + @nan.abs.should.nan? # have to do it this way, since == doesn't work on NaN + @zero.abs.should == 0 + @zero.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero_pos.abs.should == 0 + @zero_pos.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero_neg.abs.should == 0 + @zero_neg.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO + end + +end diff --git a/spec/ruby/library/bigdecimal/add_spec.rb b/spec/ruby/library/bigdecimal/add_spec.rb new file mode 100644 index 0000000000..9cdab7d910 --- /dev/null +++ b/spec/ruby/library/bigdecimal/add_spec.rb @@ -0,0 +1,185 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +require 'bigdecimal' + +describe "BigDecimal#add" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @ten = BigDecimal("10") + @eleven = BigDecimal("11") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @frac_3 = BigDecimal("12345E10") + @frac_4 = BigDecimal("98765E10") + @dot_ones = BigDecimal("0.1111111111") + end + + it "returns a + b with given precision" do + # documentation states that precision is optional, but it ain't, + @two.add(@one, 1).should == @three + @one .add(@two, 1).should == @three + @one.add(@one_minus, 1).should == @zero + @ten.add(@one, 2).should == @eleven + @zero.add(@one, 1).should == @one + @frac_2.add(@frac_1, 10000).should == BigDecimal("1.9E-99999") + @frac_1.add(@frac_1, 10000).should == BigDecimal("2E-99999") + @frac_3.add(@frac_4, 0).should == BigDecimal("0.11111E16") + @frac_3.add(@frac_4, 1).should == BigDecimal("0.1E16") + @frac_3.add(@frac_4, 2).should == BigDecimal("0.11E16") + @frac_3.add(@frac_4, 3).should == BigDecimal("0.111E16") + @frac_3.add(@frac_4, 4).should == BigDecimal("0.1111E16") + @frac_3.add(@frac_4, 5).should == BigDecimal("0.11111E16") + @frac_3.add(@frac_4, 6).should == BigDecimal("0.11111E16") + end + + it "returns a + [Fixnum value] with given precision" do + (1..10).each {|precision| + @dot_ones.add(0, precision).should == BigDecimal("0." + "1" * precision) + } + BigDecimal("0.88").add(0, 1).should == BigDecimal("0.9") + end + + it "returns a + [Bignum value] with given precision" do + bignum = 10000000000000000000 + (1..20).each {|precision| + @dot_ones.add(bignum, precision).should == BigDecimal("0.1E20") + } + (21..30).each {|precision| + @dot_ones.add(bignum, precision).should == BigDecimal( + "0.10000000000000000000" + "1" * (precision - 20) + "E20") + } + end + +# TODO: +# https://blade.ruby-lang.org/ruby-core/17374 +# +# This doesn't work on MRI and looks like a bug to me: +# one can use BigDecimal + Float, but not Bigdecimal.add(Float) +# +# it "returns a + [Float value] with given precision" do +# (1..10).each {|precision| +# @dot_ones.add(0.0, precision).should == BigDecimal("0." + "1" * precision) +# } +# +# BigDecimal("0.88").add(0.0, 1).should == BigDecimal("0.9") +# end + + describe "with Rational" do + it "produces a BigDecimal" do + (@three + Rational(500, 2)).should == BigDecimal("0.253e3") + end + end + + it "favors the precision specified in the second argument over the global limit" do + BigDecimalSpecs.with_limit(1) do + BigDecimal('0.888').add(@zero, 3).should == BigDecimal('0.888') + end + + BigDecimalSpecs.with_limit(2) do + BigDecimal('0.888').add(@zero, 1).should == BigDecimal('0.9') + end + end + + it "uses the current rounding mode if rounding is needed" do + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_UP) do + BigDecimal('0.111').add(@zero, 1).should == BigDecimal('0.2') + BigDecimal('-0.111').add(@zero, 1).should == BigDecimal('-0.2') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_DOWN) do + BigDecimal('0.999').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.999').add(@zero, 1).should == BigDecimal('-0.9') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_UP) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_DOWN) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_EVEN) do + BigDecimal('0.75').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('-0.75').add(@zero, 1).should == BigDecimal('-0.8') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_CEILING) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_FLOOR) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') + end + end + + it "uses the default ROUND_HALF_UP rounding if it wasn't explicitly changed" do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') + end + + it "returns NaN if NaN is involved" do + @one.add(@nan, 10000).should.nan? + @nan.add(@one, 1).should.nan? + end + + it "returns Infinity or -Infinity if these are involved" do + @zero.add(@infinity, 1).should == @infinity + @frac_2.add(@infinity, 1).should == @infinity + @one_minus.add(@infinity, 1).should == @infinity + @two.add(@infinity, 1).should == @infinity + + @zero.add(@infinity_minus, 1).should == @infinity_minus + @frac_2.add(@infinity_minus, 1).should == @infinity_minus + @one_minus.add(@infinity_minus, 1).should == @infinity_minus + @two.add(@infinity_minus, 1).should == @infinity_minus + + @infinity.add(@zero, 1).should == @infinity + @infinity.add(@frac_2, 1).should == @infinity + @infinity.add(@one_minus, 1).should == @infinity + @infinity.add(@two, 1).should == @infinity + + @infinity_minus.add(@zero, 1).should == @infinity_minus + @infinity_minus.add(@frac_2, 1).should == @infinity_minus + @infinity_minus.add(@one_minus, 1).should == @infinity_minus + @infinity_minus.add(@two, 1).should == @infinity_minus + + @infinity.add(@infinity, 10000).should == @infinity + @infinity_minus.add(@infinity_minus, 10000).should == @infinity_minus + end + + it "returns NaN if Infinity + (- Infinity)" do + @infinity.add(@infinity_minus, 10000).should.nan? + @infinity_minus.add(@infinity, 10000).should.nan? + end + + it "raises TypeError when adds nil" do + -> { + @one.add(nil, 10) + }.should raise_error(TypeError) + -> { + @one.add(nil, 0) + }.should raise_error(TypeError) + end + + it "raises TypeError when precision parameter is nil" do + -> { + @one.add(@one, nil) + }.should raise_error(TypeError) + end + + it "raises ArgumentError when precision parameter is negative" do + -> { + @one.add(@one, -10) + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/bigdecimal/case_compare_spec.rb b/spec/ruby/library/bigdecimal/case_compare_spec.rb new file mode 100644 index 0000000000..fac6714356 --- /dev/null +++ b/spec/ruby/library/bigdecimal/case_compare_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/eql' + + +describe "BigDecimal#===" do + it_behaves_like :bigdecimal_eql, :=== +end diff --git a/spec/ruby/library/bigdecimal/ceil_spec.rb b/spec/ruby/library/bigdecimal/ceil_spec.rb new file mode 100644 index 0000000000..60e71b12fb --- /dev/null +++ b/spec/ruby/library/bigdecimal/ceil_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#ceil" do + before :each do + @zero = BigDecimal("0") + @one = BigDecimal("1") + @three = BigDecimal("3") + @four = BigDecimal("4") + @mixed = BigDecimal("1.23456789") + @mixed_big = BigDecimal("1.23456789E100") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end + + it "returns an Integer, if n is unspecified" do + @mixed.ceil.kind_of?(Integer).should == true + end + + it "returns a BigDecimal, if n is specified" do + @pos_int.ceil(2).kind_of?(BigDecimal).should == true + end + + it "returns the smallest integer greater or equal to self, if n is unspecified" do + @pos_int.ceil.should == @pos_int + @neg_int.ceil.should == @neg_int + @pos_frac.ceil.should == BigDecimal("1") + @neg_frac.ceil.should == @zero + @zero.ceil.should == 0 + @zero_pos.ceil.should == @zero_pos + @zero_neg.ceil.should == @zero_neg + + + BigDecimal('2.3').ceil.should == 3 + BigDecimal('2.5').ceil.should == 3 + BigDecimal('2.9999').ceil.should == 3 + BigDecimal('-2.3').ceil.should == -2 + BigDecimal('-2.5').ceil.should == -2 + BigDecimal('-2.9999').ceil.should == -2 + end + + it "raise exception, if self is special value" do + -> { @infinity.ceil }.should raise_error(FloatDomainError) + -> { @infinity_neg.ceil }.should raise_error(FloatDomainError) + -> { @nan.ceil }.should raise_error(FloatDomainError) + end + + it "returns n digits right of the decimal point if given n > 0" do + @mixed.ceil(1).should == BigDecimal("1.3") + @mixed.ceil(5).should == BigDecimal("1.23457") + + BigDecimal("-0.03").ceil(1).should == BigDecimal("0") + BigDecimal("0.03").ceil(1).should == BigDecimal("0.1") + + BigDecimal("23.45").ceil(0).should == BigDecimal('24') + BigDecimal("23.45").ceil(1).should == BigDecimal('23.5') + BigDecimal("23.45").ceil(2).should == BigDecimal('23.45') + + BigDecimal("-23.45").ceil(0).should == BigDecimal('-23') + BigDecimal("-23.45").ceil(1).should == BigDecimal('-23.4') + BigDecimal("-23.45").ceil(2).should == BigDecimal('-23.45') + + BigDecimal("2E-10").ceil(0).should == @one + BigDecimal("2E-10").ceil(9).should == BigDecimal('1E-9') + BigDecimal("2E-10").ceil(10).should == BigDecimal('2E-10') + BigDecimal("2E-10").ceil(11).should == BigDecimal('2E-10') + + (1..10).each do |n| + # 0.4, 0.34, 0.334, etc. + (@one.div(@three,20)).ceil(n).should == BigDecimal("0.#{'3'*(n-1)}4") + # 1.4, 1.34, 1.334, etc. + (@four.div(@three,20)).ceil(n).should == BigDecimal("1.#{'3'*(n-1)}4") + (BigDecimal('31').div(@three,20)).ceil(n).should == BigDecimal("10.#{'3'*(n-1)}4") + end + (1..10).each do |n| + # -0.4, -0.34, -0.334, etc. + (-@one.div(@three,20)).ceil(n).should == BigDecimal("-0.#{'3'* n}") + end + (1..10).each do |n| + (@three.div(@one,20)).ceil(n).should == @three + end + (1..10).each do |n| + (-@three.div(@one,20)).ceil(n).should == -@three + end + end + + it "sets n digits left of the decimal point to 0, if given n < 0" do + BigDecimal("13345.234").ceil(-2).should == BigDecimal("13400.0") + @mixed_big.ceil(-99).should == BigDecimal("0.13E101") + @mixed_big.ceil(-100).should == BigDecimal("0.2E101") + @mixed_big.ceil(-95).should == BigDecimal("0.123457E101") + BigDecimal("1E10").ceil(-30).should == BigDecimal('1E30') + BigDecimal("-1E10").ceil(-30).should == @zero + end + +end diff --git a/spec/ruby/library/bigdecimal/clone_spec.rb b/spec/ruby/library/bigdecimal/clone_spec.rb new file mode 100644 index 0000000000..b3a1c61d6a --- /dev/null +++ b/spec/ruby/library/bigdecimal/clone_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/clone' + +describe "BigDecimal#dup" do + it_behaves_like :bigdecimal_clone, :clone +end diff --git a/spec/ruby/library/bigdecimal/coerce_spec.rb b/spec/ruby/library/bigdecimal/coerce_spec.rb new file mode 100644 index 0000000000..1e5c73f969 --- /dev/null +++ b/spec/ruby/library/bigdecimal/coerce_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#coerce" do + + it "returns [other, self] both as BigDecimal" do + one = BigDecimal("1.0") + five_point_28 = BigDecimal("5.28") + zero_minus = BigDecimal("-0.0") + some_value = 32434234234234234234 + + BigDecimal("1.2").coerce(1).should == [one, BigDecimal("1.2")] + five_point_28.coerce(1.0).should == [one, BigDecimal("5.28")] + one.coerce(one).should == [one, one] + one.coerce(2.5).should == [2.5, one] + BigDecimal("1").coerce(3.14).should == [3.14, one] + a, b = zero_minus.coerce(some_value) + a.should == BigDecimal(some_value.to_s) + b.should == zero_minus + a, b = one.coerce(some_value) + a.should == BigDecimal(some_value.to_s) + b.to_f.should be_close(1.0, TOLERANCE) # can we take out the to_f once BigDecimal#- is implemented? + b.should == one + end + +end diff --git a/spec/ruby/library/bigdecimal/comparison_spec.rb b/spec/ruby/library/bigdecimal/comparison_spec.rb new file mode 100644 index 0000000000..c53187b727 --- /dev/null +++ b/spec/ruby/library/bigdecimal/comparison_spec.rb @@ -0,0 +1,81 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#<=>" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @mixed_big = BigDecimal("1.23456789E100") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def >=(other) + BigDecimal('123') >= other + end + end + + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + end + + + it "returns 0 if a == b" do + (@pos_int <=> @pos_int).should == 0 + (@neg_int <=> @neg_int).should == 0 + (@pos_frac <=> @pos_frac).should == 0 + (@neg_frac <=> @neg_frac).should == 0 + (@zero <=> @zero).should == 0 + (@infinity <=> @infinity).should == 0 + (@infinity_neg <=> @infinity_neg).should == 0 + end + + it "returns 1 if a > b" do + (@pos_int <=> @neg_int).should == 1 + (@pos_frac <=> @neg_frac).should == 1 + (@pos_frac <=> @zero).should == 1 + @values.each { |val| + (@infinity <=> val).should == 1 + } + end + + it "returns -1 if a < b" do + (@zero <=> @pos_frac).should == -1 + (@neg_int <=> @pos_frac).should == -1 + (@pos_frac <=> @pos_int).should == -1 + @values.each { |val| + (@infinity_neg <=> val).should == -1 + } + end + + it "returns nil if NaN is involved" do + @values += [@infinity, @infinity_neg, @nan] + @values << nil + @values << Object.new + @values.each { |val| + (@nan <=> val).should == nil + } + end + + it "returns nil if the argument is nil" do + (@zero <=> nil).should == nil + (@infinity <=> nil).should == nil + (@infinity_neg <=> nil).should == nil + (@mixed <=> nil).should == nil + (@pos_int <=> nil).should == nil + (@neg_frac <=> nil).should == nil + end +end diff --git a/spec/ruby/library/bigdecimal/constants_spec.rb b/spec/ruby/library/bigdecimal/constants_spec.rb new file mode 100644 index 0000000000..8d879c036a --- /dev/null +++ b/spec/ruby/library/bigdecimal/constants_spec.rb @@ -0,0 +1,70 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal constants" do + it "defines a VERSION value" do + BigDecimal.const_defined?(:VERSION).should be_true + end + + it "has a BASE value" do + # The actual one is decided based on HAVE_INT64_T in MRI, + # which is hard to check here. + [10000, 1000000000].should include(BigDecimal::BASE) + end + + it "has a NaN value" do + BigDecimal::NAN.nan?.should be_true + end + + it "has an INFINITY value" do + BigDecimal::INFINITY.infinite?.should == 1 + end + + describe "exception-related constants" do + [ + [:EXCEPTION_ALL, 0xff], + [:EXCEPTION_INFINITY, 0x01], + [:EXCEPTION_NaN, 0x02], + [:EXCEPTION_UNDERFLOW, 0x04], + [:EXCEPTION_OVERFLOW, 0x01], + [:EXCEPTION_ZERODIVIDE, 0x10] + ].each do |const, value| + it "has a #{const} value" do + BigDecimal.const_get(const).should == value + end + end + end + + describe "rounding-related constants" do + [ + [:ROUND_MODE, 0x100], + [:ROUND_UP, 1], + [:ROUND_DOWN, 2], + [:ROUND_HALF_UP, 3], + [:ROUND_HALF_DOWN, 4], + [:ROUND_CEILING, 5], + [:ROUND_FLOOR, 6], + [:ROUND_HALF_EVEN, 7] + ].each do |const, value| + it "has a #{const} value" do + BigDecimal.const_get(const).should == value + end + end + end + + describe "sign-related constants" do + [ + [:SIGN_NaN, 0], + [:SIGN_POSITIVE_ZERO, 1], + [:SIGN_NEGATIVE_ZERO, -1], + [:SIGN_POSITIVE_FINITE, 2], + [:SIGN_NEGATIVE_FINITE, -2], + [:SIGN_POSITIVE_INFINITE, 3], + [:SIGN_NEGATIVE_INFINITE, -3] + ].each do |const, value| + it "has a #{const} value" do + BigDecimal.const_get(const).should == value + end + end + end +end diff --git a/spec/ruby/library/bigdecimal/core_spec.rb b/spec/ruby/library/bigdecimal/core_spec.rb new file mode 100644 index 0000000000..5097d70865 --- /dev/null +++ b/spec/ruby/library/bigdecimal/core_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "Core extension by bigdecimal" do + context "Integer#coerce" do + it "produces Floats" do + x, y = 3.coerce(BigDecimal("3.4")) + x.class.should == Float + x.should == 3.4 + y.class.should == Float + y.should == 3.0 + end + end + + describe "Time.at passed BigDecimal" do + it "doesn't round input value" do + Time.at(BigDecimal('1.1')).to_f.should == 1.1 + end + end + + describe "BigDecimal#log" do + it "handles high-precision Rational arguments" do + # log(BigDecimal(r, 50), 50) + result1 = BigDecimal('0.22314354220170971436137296411949880462556361100856e0') + # log(BigDecimal(r, 1000), 50) + result2 = BigDecimal('0.22314354220170971436137296411949880462556361100853e0') + r = Rational(1_234_567_890, 987_654_321) + [result1, result2].should include(BigMath.log(r, 50).mult(1, 50)) + end + end + + describe "Rational#coerce" do + it "returns the passed argument, self as Float, when given a Float" do + result = Rational(3, 4).coerce(1.0) + result.should == [1.0, 0.75] + result.first.is_a?(Float).should be_true + result.last.is_a?(Float).should be_true + end + + it "returns the passed argument, self as Rational, when given an Integer" do + result = Rational(3, 4).coerce(10) + result.should == [Rational(10, 1), Rational(3, 4)] + result.first.is_a?(Rational).should be_true + result.last.is_a?(Rational).should be_true + end + + it "coerces to Rational, when given a Complex" do + Rational(3, 4).coerce(Complex(5)).should == [Rational(5, 1), Rational(3, 4)] + Rational(12, 4).coerce(Complex(5, 1)).should == [Complex(5, 1), Complex(3)] + end + + it "returns [argument, self] when given a Rational" do + Rational(3, 7).coerce(Rational(9, 2)).should == [Rational(9, 2), Rational(3, 7)] + end + + it "raises an error when passed a BigDecimal" do + -> { + Rational(500, 3).coerce(BigDecimal('166.666666666')) + }.should raise_error(TypeError, /BigDecimal can't be coerced into Rational/) + end + end +end diff --git a/spec/ruby/library/bigdecimal/div_spec.rb b/spec/ruby/library/bigdecimal/div_spec.rb new file mode 100644 index 0000000000..53ad6d0418 --- /dev/null +++ b/spec/ruby/library/bigdecimal/div_spec.rb @@ -0,0 +1,110 @@ +require_relative '../../spec_helper' +require_relative 'shared/quo' +require 'bigdecimal' + +describe "BigDecimal#div with precision set to 0" do + # TODO: figure out if there is a better way to do these + # shared specs rather than sending [0]. See other specs + # that share :bigdecimal_quo. + it_behaves_like :bigdecimal_quo, :div, [0] +end + +describe "BigDecimal#div" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_plus = BigDecimal("+0") + @zero_minus = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns a / b with optional precision" do + @two.div(@one).should == @two + @one.div(@two).should == @zero + # ^^ is this really intended for a class with arbitrary precision? + @one.div(@two, 1).should == BigDecimal("0.5") + @one.div(@one_minus).should == @one_minus + @one_minus.div(@one_minus).should == @one + @frac_2.div(@frac_1, 1).should == BigDecimal("0.9") + @frac_1.div(@frac_1).should == @one + + res = "0." + "3" * 1000 + (1..100).each { |idx| + @one.div(@three, idx).to_s("F").should == "0." + res[2, idx] + } + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, @two]) + @one.div(object).should == @zero + end + end + + it "raises FloatDomainError if NaN is involved" do + -> { @one.div(@nan) }.should raise_error(FloatDomainError) + -> { @nan.div(@one) }.should raise_error(FloatDomainError) + -> { @nan.div(@nan) }.should raise_error(FloatDomainError) + end + + it "returns 0 if divided by Infinity and no precision given" do + @zero.div(@infinity).should == 0 + @frac_2.div(@infinity).should == 0 + end + + it "returns 0 if divided by Infinity with given precision" do + @zero.div(@infinity, 0).should == 0 + @frac_2.div(@infinity, 1).should == 0 + @zero.div(@infinity, 100000).should == 0 + @frac_2.div(@infinity, 100000).should == 0 + end + + it "raises ZeroDivisionError if divided by zero and no precision given" do + -> { @one.div(@zero) }.should raise_error(ZeroDivisionError) + -> { @one.div(@zero_plus) }.should raise_error(ZeroDivisionError) + -> { @one.div(@zero_minus) }.should raise_error(ZeroDivisionError) + + -> { @zero.div(@zero) }.should raise_error(ZeroDivisionError) + -> { @zero_minus.div(@zero_plus) }.should raise_error(ZeroDivisionError) + -> { @zero_minus.div(@zero_minus) }.should raise_error(ZeroDivisionError) + -> { @zero_plus.div(@zero_minus) }.should raise_error(ZeroDivisionError) + end + + it "returns NaN if zero is divided by zero" do + @zero.div(@zero, 0).should.nan? + @zero_minus.div(@zero_plus, 0).should.nan? + @zero_plus.div(@zero_minus, 0).should.nan? + + @zero.div(@zero, 10).should.nan? + @zero_minus.div(@zero_plus, 10).should.nan? + @zero_plus.div(@zero_minus, 10).should.nan? + end + + it "raises FloatDomainError if (+|-) Infinity divided by 1 and no precision given" do + -> { @infinity_minus.div(@one) }.should raise_error(FloatDomainError) + -> { @infinity.div(@one) }.should raise_error(FloatDomainError) + -> { @infinity_minus.div(@one_minus) }.should raise_error(FloatDomainError) + end + + it "returns (+|-)Infinity if (+|-)Infinity by 1 and precision given" do + @infinity_minus.div(@one, 0).should == @infinity_minus + @infinity.div(@one, 0).should == @infinity + @infinity_minus.div(@one_minus, 0).should == @infinity + end + + it "returns NaN if Infinity / ((+|-) Infinity)" do + @infinity.div(@infinity_minus, 100000).should.nan? + @infinity_minus.div(@infinity, 1).should.nan? + end + + +end diff --git a/spec/ruby/library/bigdecimal/divide_spec.rb b/spec/ruby/library/bigdecimal/divide_spec.rb new file mode 100644 index 0000000000..c62b23557d --- /dev/null +++ b/spec/ruby/library/bigdecimal/divide_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require_relative 'shared/quo' +require 'bigdecimal' + +describe "BigDecimal#/" do + it_behaves_like :bigdecimal_quo, :/, [] + + before :each do + @three = BigDecimal("3") + end + + describe "with Rational" do + it "produces a BigDecimal" do + (@three / Rational(500, 2)).should == BigDecimal("0.12e-1") + end + end +end diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb new file mode 100644 index 0000000000..85c014bb8c --- /dev/null +++ b/spec/ruby/library/bigdecimal/divmod_spec.rb @@ -0,0 +1,212 @@ +require_relative '../../spec_helper' +require_relative 'shared/modulo' +require 'bigdecimal' + +module DivmodSpecs + def self.check_both_nan(array) + array.length.should == 2 + array[0].should.nan? + array[1].should.nan? + end + def self.check_both_bigdecimal(array) + array.length.should == 2 + array[0].kind_of?(BigDecimal).should == true + array[1].kind_of?(BigDecimal).should == true + end +end + +# TODO: figure out a way to do the shared specs with helpers instead +# of spec'ing a method that does not really exist +describe "BigDecimal#mod_part_of_divmod" do + # BigDecimal#divmod[1] behaves exactly like #modulo + before :all do + class BigDecimal + def mod_part_of_divmod(arg) + divmod(arg)[1] + end + end + end + + after :all do + class BigDecimal + undef mod_part_of_divmod + end + end + + version_is BigDecimal::VERSION, ""..."4.0.0" do + it_behaves_like :bigdecimal_modulo, :mod_part_of_divmod + end + + it "raises ZeroDivisionError if other is zero" do + bd5667 = BigDecimal("5667.19") + zero = BigDecimal("0") + -> { bd5667.mod_part_of_divmod(0) }.should raise_error(ZeroDivisionError) + -> { bd5667.mod_part_of_divmod(BigDecimal("0")) }.should raise_error(ZeroDivisionError) + -> { zero.mod_part_of_divmod(zero) }.should raise_error(ZeroDivisionError) + end +end + +describe "BigDecimal#divmod" do + + before :each do + @a = BigDecimal("42.00000000000000000001") + + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + + @one = BigDecimal("1") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + + @special_vals = [@infinity, @infinity_minus, @nan] + @regular_vals = [ + @one, @mixed, @pos_int, @neg_int, @pos_frac, + @neg_frac, @one_minus, @frac_1, @frac_2] + @zeroes = [@zero, @zero_pos, @zero_neg] + end + + version_is BigDecimal::VERSION, ""..."4.0.0" do + it "divides value, returns [BigDecimal, BigDecimal]" do + res = @a.divmod(5) + res.kind_of?(Array).should == true + DivmodSpecs.check_both_bigdecimal(res) + end + end + + version_is BigDecimal::VERSION, "4.0.0" do + it "divides value, returns [Integer, BigDecimal]" do + res = @a.divmod(5) + res.kind_of?(Array).should == true + res[0].kind_of?(Integer).should == true + res[1].kind_of?(BigDecimal).should == true + end + end + + it "array contains quotient and modulus as BigDecimal" do + res = @a.divmod(5) + res[0].should == BigDecimal('0.8E1') + res[1].should == BigDecimal('2.00000000000000000001') + + BigDecimal('1').divmod(BigDecimal('2')).should == [0, 1] + BigDecimal('2').divmod(BigDecimal('1')).should == [2, 0] + + BigDecimal('1').divmod(BigDecimal('-2')).should == [-1, -1] + BigDecimal('2').divmod(BigDecimal('-1')).should == [-2, 0] + + BigDecimal('-1').divmod(BigDecimal('2')).should == [-1, 1] + BigDecimal('-2').divmod(BigDecimal('1')).should == [-2, 0] + end + + it "can be reversed with * and +" do + # Example taken from BigDecimal documentation + a = BigDecimal("42") + b = BigDecimal("9") + q, m = a.divmod(b) + c = q * b + m + a.should == c + + values = [@one, @one_minus, BigDecimal('2'), BigDecimal('-2'), + BigDecimal('5'), BigDecimal('-5'), BigDecimal('10'), BigDecimal('-10'), + BigDecimal('20'), BigDecimal('-20'), BigDecimal('100'), BigDecimal('-100'), + BigDecimal('1.23456789E10'), BigDecimal('-1.23456789E10') + ] + + # TODO: file MRI bug: + # BigDecimal('1').divmod(BigDecimal('3E-9'))[0] #=> 0.3E9, + # but really should be 0.333333333E9 + values << BigDecimal('1E-10') + values << BigDecimal('-1E-10') + values << BigDecimal('2E55') + values << BigDecimal('-2E55') + values << BigDecimal('2E-5555') + values << BigDecimal('-2E-5555') + + + values_and_zeroes = values + @zeroes + values_and_zeroes.each do |val1| + values.each do |val2| + res = val1.divmod(val2) + res[0].should == ((val1/val2).floor) + res[1].should == (val1 - res[0] * val2) + end + end + end + + version_is BigDecimal::VERSION, "4.0.0" do + it "raise FloatDomainError error if NaN is involved" do + (@special_vals + @regular_vals + @zeroes).each do |val| + -> { val.divmod(@nan) }.should raise_error(FloatDomainError) + -> { @nan.divmod(val) }.should raise_error(FloatDomainError) + end + end + end + + version_is BigDecimal::VERSION, ""..."4.0.0" do + it "returns an array of two NaNs if NaN is involved" do + (@special_vals + @regular_vals + @zeroes).each do |val| + DivmodSpecs.check_both_nan(val.divmod(@nan)) + DivmodSpecs.check_both_nan(@nan.divmod(val)) + end + end + end + + it "raises ZeroDivisionError if the divisor is zero" do + (@special_vals + @regular_vals + @zeroes - [@nan]).each do |val| + @zeroes.each do |zero| + -> { val.divmod(zero) }.should raise_error(ZeroDivisionError) + end + end + end + + version_is BigDecimal::VERSION, ""..."4.0.0" do + it "returns an array of Infinity and NaN if the dividend is Infinity" do + @regular_vals.each do |val| + array = @infinity.divmod(val) + array.length.should == 2 + array[0].infinite?.should == (val > 0 ? 1 : -1) + array[1].should.nan? + end + end + end + + version_is BigDecimal::VERSION, "3.3.0" do + it "returns an array of zero and the dividend or minus one and Infinity if the divisor is Infinity" do + @regular_vals.each do |val| + array = val.divmod(@infinity) + array.length.should == 2 + if val >= 0 + array[0].should == @zero + array[1].should == val + else + array[0].should == @one_minus + array[1].should == @infinity + end + end + end + end + + it "returns an array of two zero if the dividend is zero" do + @zeroes.each do |zero| + @regular_vals.each do |val| + zero.divmod(val).should == [@zero, @zero] + end + end + end + + it "raises TypeError if the argument cannot be coerced to BigDecimal" do + -> { + @one.divmod('1') + }.should raise_error(TypeError) + end + +end diff --git a/spec/ruby/library/bigdecimal/double_fig_spec.rb b/spec/ruby/library/bigdecimal/double_fig_spec.rb new file mode 100644 index 0000000000..f742d68f87 --- /dev/null +++ b/spec/ruby/library/bigdecimal/double_fig_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal.double_fig" do + # The result depends on the CPU and OS + it "returns the number of digits a Float number is allowed to have" do + BigDecimal.double_fig.should_not == nil + end +end diff --git a/spec/ruby/library/bigdecimal/dup_spec.rb b/spec/ruby/library/bigdecimal/dup_spec.rb new file mode 100644 index 0000000000..bfabaf6e8b --- /dev/null +++ b/spec/ruby/library/bigdecimal/dup_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/clone' + +describe "BigDecimal#dup" do + it_behaves_like :bigdecimal_clone, :dup +end diff --git a/spec/ruby/library/bigdecimal/eql_spec.rb b/spec/ruby/library/bigdecimal/eql_spec.rb new file mode 100644 index 0000000000..1be5862751 --- /dev/null +++ b/spec/ruby/library/bigdecimal/eql_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/eql' + +describe "BigDecimal#eql?" do + it_behaves_like :bigdecimal_eql, :eql? +end diff --git a/spec/ruby/library/bigdecimal/equal_value_spec.rb b/spec/ruby/library/bigdecimal/equal_value_spec.rb new file mode 100644 index 0000000000..933060eada --- /dev/null +++ b/spec/ruby/library/bigdecimal/equal_value_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/eql' + + +describe "BigDecimal#==" do + it_behaves_like :bigdecimal_eql, :== +end diff --git a/spec/ruby/library/bigdecimal/exponent_spec.rb b/spec/ruby/library/bigdecimal/exponent_spec.rb new file mode 100644 index 0000000000..8877147955 --- /dev/null +++ b/spec/ruby/library/bigdecimal/exponent_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require_relative 'shared/power' +require 'bigdecimal' + +describe "BigDecimal#**" do + it_behaves_like :bigdecimal_power, :** +end + +describe "BigDecimal#exponent" do + + it "returns an Integer" do + BigDecimal("2E100000000").exponent.kind_of?(Integer).should == true + BigDecimal("2E-999").exponent.kind_of?(Integer).should == true + end + + it "is n if number can be represented as 0.xxx*10**n" do + BigDecimal("2E1000").exponent.should == 1001 + BigDecimal("1234567E10").exponent.should == 17 + end + + it "returns 0 if self is 0" do + BigDecimal("0").exponent.should == 0 + BigDecimal("+0").exponent.should == 0 + BigDecimal("-0").exponent.should == 0 + end + +end diff --git a/spec/ruby/library/bigdecimal/finite_spec.rb b/spec/ruby/library/bigdecimal/finite_spec.rb new file mode 100644 index 0000000000..8fc06029bb --- /dev/null +++ b/spec/ruby/library/bigdecimal/finite_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#finite?" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @big = BigDecimal("2E40001") + @finite_vals = [@one, @zero, @zero_pos, @zero_neg, @two, + @three, @frac_1, @frac_2, @big, @one_minus] + end + + it "is false if Infinity or NaN" do + @infinity.should_not.finite? + @infinity_minus.should_not.finite? + @nan.should_not.finite? + end + + it "returns true for finite values" do + @finite_vals.each do |val| + val.should.finite? + end + end +end diff --git a/spec/ruby/library/bigdecimal/fix_spec.rb b/spec/ruby/library/bigdecimal/fix_spec.rb new file mode 100644 index 0000000000..2c6276899e --- /dev/null +++ b/spec/ruby/library/bigdecimal/fix_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#fix" do + before :each do + @zero = BigDecimal("0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end + + it "returns a BigDecimal" do + BigDecimal("2E100000000").fix.kind_of?(BigDecimal).should == true + BigDecimal("2E-999").kind_of?(BigDecimal).should == true + end + + it "returns the integer part of the absolute value" do + a = BigDecimal("2E1000") + a.fix.should == a + b = BigDecimal("-2E1000") + b.fix.should == b + BigDecimal("0.123456789E5").fix.should == BigDecimal("0.12345E5") + BigDecimal("-0.123456789E5").fix.should == BigDecimal("-0.12345E5") + end + + it "correctly handles special values" do + @infinity.fix.should == @infinity + @infinity_neg.fix.should == @infinity_neg + @nan.fix.should.nan? + end + + it "returns 0 if the absolute value is < 1" do + BigDecimal("0.99999").fix.should == 0 + BigDecimal("-0.99999").fix.should == 0 + BigDecimal("0.000000001").fix.should == 0 + BigDecimal("-0.00000001").fix.should == 0 + BigDecimal("-1000000").fix.should_not == 0 + @zero.fix.should == 0 + @zero_pos.fix.should == @zero_pos + @zero_neg.fix.should == @zero_neg + end + + it "does not allow any arguments" do + -> { + @mixed.fix(10) + }.should raise_error(ArgumentError) + end + +end diff --git a/spec/ruby/library/bigdecimal/fixtures/classes.rb b/spec/ruby/library/bigdecimal/fixtures/classes.rb new file mode 100644 index 0000000000..06e4474cf0 --- /dev/null +++ b/spec/ruby/library/bigdecimal/fixtures/classes.rb @@ -0,0 +1,17 @@ +module BigDecimalSpecs + # helper method to sure that the global limit is reset back + def self.with_limit(l) + old = BigDecimal.limit(l) + yield + ensure + BigDecimal.limit(old) + end + + def self.with_rounding(r) + old = BigDecimal.mode(BigDecimal::ROUND_MODE) + BigDecimal.mode(BigDecimal::ROUND_MODE, r) + yield + ensure + BigDecimal.mode(BigDecimal::ROUND_MODE, old) + end +end diff --git a/spec/ruby/library/bigdecimal/floor_spec.rb b/spec/ruby/library/bigdecimal/floor_spec.rb new file mode 100644 index 0000000000..a7dfec2c9a --- /dev/null +++ b/spec/ruby/library/bigdecimal/floor_spec.rb @@ -0,0 +1,100 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#floor" do + before :each do + @one = BigDecimal("1") + @three = BigDecimal("3") + @four = BigDecimal("4") + @zero = BigDecimal("0") + @mixed = BigDecimal("1.23456789") + @mixed_big = BigDecimal("1.23456789E100") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end + + it "returns the greatest integer smaller or equal to self" do + @pos_int.floor.should == @pos_int + @neg_int.floor.should == @neg_int + @pos_frac.floor.should == @zero + @neg_frac.floor.should == BigDecimal("-1") + @zero.floor.should == 0 + @zero_pos.floor.should == @zero_pos + @zero_neg.floor.should == @zero_neg + + BigDecimal('2.3').floor.should == 2 + BigDecimal('2.5').floor.should == 2 + BigDecimal('2.9999').floor.should == 2 + BigDecimal('-2.3').floor.should == -3 + BigDecimal('-2.5').floor.should == -3 + BigDecimal('-2.9999').floor.should == -3 + BigDecimal('0.8').floor.should == 0 + BigDecimal('-0.8').floor.should == -1 + end + + it "raise exception, if self is special value" do + -> { @infinity.floor }.should raise_error(FloatDomainError) + -> { @infinity_neg.floor }.should raise_error(FloatDomainError) + -> { @nan.floor }.should raise_error(FloatDomainError) + end + + it "returns n digits right of the decimal point if given n > 0" do + @mixed.floor(1).should == BigDecimal("1.2") + @mixed.floor(5).should == BigDecimal("1.23456") + + BigDecimal("-0.03").floor(1).should == BigDecimal("-0.1") + BigDecimal("0.03").floor(1).should == BigDecimal("0") + + BigDecimal("23.45").floor(0).should == BigDecimal('23') + BigDecimal("23.45").floor(1).should == BigDecimal('23.4') + BigDecimal("23.45").floor(2).should == BigDecimal('23.45') + + BigDecimal("-23.45").floor(0).should == BigDecimal('-24') + BigDecimal("-23.45").floor(1).should == BigDecimal('-23.5') + BigDecimal("-23.45").floor(2).should == BigDecimal('-23.45') + + BigDecimal("2E-10").floor(0).should == @zero + BigDecimal("2E-10").floor(9).should == @zero + BigDecimal("2E-10").floor(10).should == BigDecimal('2E-10') + BigDecimal("2E-10").floor(11).should == BigDecimal('2E-10') + + (1..10).each do |n| + # 0.3, 0.33, 0.333, etc. + (@one.div(@three,20)).floor(n).should == BigDecimal("0.#{'3'*n}") + # 1.3, 1.33, 1.333, etc. + (@four.div(@three,20)).floor(n).should == BigDecimal("1.#{'3'*n}") + (BigDecimal('31').div(@three,20)).floor(n).should == BigDecimal("10.#{'3'*n}") + end + (1..10).each do |n| + # -0.4, -0.34, -0.334, etc. + (-@one.div(@three,20)).floor(n).should == BigDecimal("-0.#{'3'*(n-1)}4") + end + (1..10).each do |n| + (@three.div(@one,20)).floor(n).should == @three + end + (1..10).each do |n| + (-@three.div(@one,20)).floor(n).should == -@three + end + end + + it "sets n digits left of the decimal point to 0, if given n < 0" do + BigDecimal("13345.234").floor(-2).should == BigDecimal("13300.0") + @mixed_big.floor(-99).should == BigDecimal("0.12E101") + @mixed_big.floor(-100).should == BigDecimal("0.1E101") + @mixed_big.floor(-95).should == BigDecimal("0.123456E101") + (1..10).each do |n| + BigDecimal('1.8').floor(-n).should == @zero + end + BigDecimal("1E10").floor(-30).should == @zero + BigDecimal("-1E10").floor(-30).should == BigDecimal('-1E30') + end + +end diff --git a/spec/ruby/library/bigdecimal/frac_spec.rb b/spec/ruby/library/bigdecimal/frac_spec.rb new file mode 100644 index 0000000000..11ccf03c2f --- /dev/null +++ b/spec/ruby/library/bigdecimal/frac_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#frac" do + before :each do + @zero = BigDecimal("0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end + + it "returns a BigDecimal" do + @pos_int.frac.kind_of?(BigDecimal).should == true + @neg_int.frac.kind_of?(BigDecimal).should == true + @pos_frac.kind_of?(BigDecimal).should == true + @neg_frac.kind_of?(BigDecimal).should == true + end + + it "returns the fractional part of the absolute value" do + @mixed.frac.should == BigDecimal("0.23456789") + @pos_frac.frac.should == @pos_frac + @neg_frac.frac.should == @neg_frac + end + + it "returns 0 if the value is 0" do + @zero.frac.should == @zero + end + + it "returns 0 if the value is an integer" do + @pos_int.frac.should == @zero + @neg_int.frac.should == @zero + end + + it "correctly handles special values" do + @infinity.frac.should == @infinity + @infinity_neg.frac.should == @infinity_neg + @nan.frac.should.nan? + end + +end diff --git a/spec/ruby/library/bigdecimal/gt_spec.rb b/spec/ruby/library/bigdecimal/gt_spec.rb new file mode 100644 index 0000000000..78547fb85f --- /dev/null +++ b/spec/ruby/library/bigdecimal/gt_spec.rb @@ -0,0 +1,96 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#>" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def >(other) + BigDecimal('123') > other + end + end + + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY + + @nan = BigDecimal("NaN") + end + + it "returns true if a > b" do + one = BigDecimal("1") + two = BigDecimal("2") + + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") + (@zero > one).should == false + (two > @zero).should == true + (frac_2 > frac_1).should == false + + (@neg_int > @pos_int).should == false + (@pos_int > @neg_int).should == true + (@neg_int > @pos_frac).should == false + (@pos_frac > @neg_int).should == true + (@zero > @zero_pos).should == false + (@zero > @zero_neg).should == false + (@zero_neg > @zero_pos).should == false + (@zero_pos > @zero_neg).should == false + end + + it "properly handles infinity values" do + @values.each { |val| + (val > @infinity).should == false + (@infinity > val).should == true + (val > @infinity_neg).should == true + (@infinity_neg > val).should == false + } + (@infinity > @infinity).should == false + (@infinity_neg > @infinity_neg).should == false + (@infinity > @infinity_neg).should == true + (@infinity_neg > @infinity).should == false + end + + it "properly handles Float infinity values" do + @values.each { |val| + (val > @float_infinity).should == false + (@float_infinity > val).should == true + (val > @float_infinity_neg).should == true + (@float_infinity_neg > val).should == false + } + end + + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan > val).should == false + (val > @nan).should == false + } + end + + it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do + -> {@zero > nil }.should raise_error(ArgumentError) + -> {@infinity > nil }.should raise_error(ArgumentError) + -> {@infinity_neg > nil }.should raise_error(ArgumentError) + -> {@mixed > nil }.should raise_error(ArgumentError) + -> {@pos_int > nil }.should raise_error(ArgumentError) + -> {@neg_frac > nil }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/bigdecimal/gte_spec.rb b/spec/ruby/library/bigdecimal/gte_spec.rb new file mode 100644 index 0000000000..2a5cc025ba --- /dev/null +++ b/spec/ruby/library/bigdecimal/gte_spec.rb @@ -0,0 +1,100 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#>=" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def >=(other) + BigDecimal('123') >= other + end + end + + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY + + @nan = BigDecimal("NaN") + end + + it "returns true if a >= b" do + one = BigDecimal("1") + two = BigDecimal("2") + + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") + + (@zero >= one).should == false + (two >= @zero).should == true + + (frac_2 >= frac_1).should == false + (two >= two).should == true + (frac_1 >= frac_1).should == true + + (@neg_int >= @pos_int).should == false + (@pos_int >= @neg_int).should == true + (@neg_int >= @pos_frac).should == false + (@pos_frac >= @neg_int).should == true + (@zero >= @zero_pos).should == true + (@zero >= @zero_neg).should == true + (@zero_neg >= @zero_pos).should == true + (@zero_pos >= @zero_neg).should == true + end + + it "properly handles infinity values" do + @values.each { |val| + (val >= @infinity).should == false + (@infinity >= val).should == true + (val >= @infinity_neg).should == true + (@infinity_neg >= val).should == false + } + (@infinity >= @infinity).should == true + (@infinity_neg >= @infinity_neg).should == true + (@infinity >= @infinity_neg).should == true + (@infinity_neg >= @infinity).should == false + end + + it "properly handles Float infinity values" do + @values.each { |val| + (val >= @float_infinity).should == false + (@float_infinity >= val).should == true + (val >= @float_infinity_neg).should == true + (@float_infinity_neg >= val).should == false + } + end + + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan >= val).should == false + (val >= @nan).should == false + } + end + + it "returns nil if the argument is nil" do + -> {@zero >= nil }.should raise_error(ArgumentError) + -> {@infinity >= nil }.should raise_error(ArgumentError) + -> {@infinity_neg >= nil }.should raise_error(ArgumentError) + -> {@mixed >= nil }.should raise_error(ArgumentError) + -> {@pos_int >= nil }.should raise_error(ArgumentError) + -> {@neg_frac >= nil }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/bigdecimal/hash_spec.rb b/spec/ruby/library/bigdecimal/hash_spec.rb new file mode 100644 index 0000000000..7581c90f68 --- /dev/null +++ b/spec/ruby/library/bigdecimal/hash_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BidDecimal#hash" do + describe "two BigDecimal objects with the same value" do + it "should have the same hash for ordinary values" do + BigDecimal('1.2920').hash.should == BigDecimal('1.2920').hash + end + + it "should have the same hash for infinite values" do + BigDecimal("+Infinity").hash.should == BigDecimal("+Infinity").hash + BigDecimal("-Infinity").hash.should == BigDecimal("-Infinity").hash + end + + it "should have the same hash for NaNs" do + BigDecimal("NaN").hash.should == BigDecimal("NaN").hash + end + + it "should have the same hash for zero values" do + BigDecimal("+0").hash.should == BigDecimal("+0").hash + BigDecimal("-0").hash.should == BigDecimal("-0").hash + end + end + + describe "two BigDecimal objects with numerically equal values" do + it "should have the same hash value" do + BigDecimal("1.2920").hash.should == BigDecimal("1.2920000").hash + end + end +end diff --git a/spec/ruby/library/bigdecimal/infinite_spec.rb b/spec/ruby/library/bigdecimal/infinite_spec.rb new file mode 100644 index 0000000000..025386107f --- /dev/null +++ b/spec/ruby/library/bigdecimal/infinite_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#infinite?" do + + it "returns 1 if self is Infinity" do + BigDecimal("Infinity").infinite?.should == 1 + end + + it "returns -1 if self is -Infinity" do + BigDecimal("-Infinity").infinite?.should == -1 + end + + it "returns not true otherwise" do + e2_plus = BigDecimal("2E40001") + e3_minus = BigDecimal("3E-20001") + really_small_zero = BigDecimal("0E-200000000") + really_big_zero = BigDecimal("0E200000000000") + e3_minus.infinite?.should == nil + e2_plus.infinite?.should == nil + really_small_zero.infinite?.should == nil + really_big_zero.infinite?.should == nil + BigDecimal("0.000000000000000000000000").infinite?.should == nil + end + + it "returns not true if self is NaN" do + # NaN is a special value which is neither finite nor infinite. + nan = BigDecimal("NaN") + nan.infinite?.should == nil + end + +end diff --git a/spec/ruby/library/bigdecimal/inspect_spec.rb b/spec/ruby/library/bigdecimal/inspect_spec.rb new file mode 100644 index 0000000000..7ce47142b2 --- /dev/null +++ b/spec/ruby/library/bigdecimal/inspect_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#inspect" do + + before :each do + @bigdec = BigDecimal("1234.5678") + end + + it "returns String" do + @bigdec.inspect.kind_of?(String).should == true + end + + it "looks like this" do + @bigdec.inspect.should == "0.12345678e4" + end + + it "does not add an exponent for zero values" do + BigDecimal("0").inspect.should == "0.0" + BigDecimal("+0").inspect.should == "0.0" + BigDecimal("-0").inspect.should == "-0.0" + end + + it "properly cases non-finite values" do + BigDecimal("NaN").inspect.should == "NaN" + BigDecimal("Infinity").inspect.should == "Infinity" + BigDecimal("+Infinity").inspect.should == "Infinity" + BigDecimal("-Infinity").inspect.should == "-Infinity" + end +end diff --git a/spec/ruby/library/bigdecimal/limit_spec.rb b/spec/ruby/library/bigdecimal/limit_spec.rb new file mode 100644 index 0000000000..75cbc8b55c --- /dev/null +++ b/spec/ruby/library/bigdecimal/limit_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'bigdecimal' + +describe "BigDecimal.limit" do + it "returns the value before set if the passed argument is nil or is not specified" do + old = BigDecimal.limit + BigDecimal.limit.should == 0 + BigDecimal.limit(10).should == 0 + BigDecimal.limit.should == 10 + BigDecimal.limit(old) + end + + it "uses the global limit if no precision is specified" do + BigDecimalSpecs.with_limit(0) do + (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.888') + (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.888') + (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.664') + (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.296') + end + + BigDecimalSpecs.with_limit(1) do + (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.9') + (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.9') + (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('3') + (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.3') + end + + BigDecimalSpecs.with_limit(2) do + (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.89') + (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.89') + (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.7') + (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.30') + end + end + + it "picks the specified precision over global limit" do + BigDecimalSpecs.with_limit(3) do + BigDecimal('0.888').add(BigDecimal('0'), 2).should == BigDecimal('0.89') + BigDecimal('0.888').sub(BigDecimal('0'), 2).should == BigDecimal('0.89') + BigDecimal('0.888').mult(BigDecimal('3'), 2).should == BigDecimal('2.7') + BigDecimal('0.888').div(BigDecimal('3'), 2).should == BigDecimal('0.30') + end + end + + it "picks the global precision when limit 0 specified" do + BigDecimalSpecs.with_limit(3) do + BigDecimal('0.8888').add(BigDecimal('0'), 0).should == BigDecimal('0.889') + BigDecimal('0.8888').sub(BigDecimal('0'), 0).should == BigDecimal('0.889') + BigDecimal('0.888').mult(BigDecimal('3'), 0).should == BigDecimal('2.66') + BigDecimal('0.8888').div(BigDecimal('3'), 0).should == BigDecimal('0.296') + end + end + +end diff --git a/spec/ruby/library/bigdecimal/lt_spec.rb b/spec/ruby/library/bigdecimal/lt_spec.rb new file mode 100644 index 0000000000..02390e76e3 --- /dev/null +++ b/spec/ruby/library/bigdecimal/lt_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#<" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def <(other) + BigDecimal('123') < other + end + end + + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY + + @nan = BigDecimal("NaN") + end + + it "returns true if a < b" do + one = BigDecimal("1") + two = BigDecimal("2") + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") + (@zero < one).should == true + (two < @zero).should == false + (frac_2 < frac_1).should == true + (@neg_int < @pos_int).should == true + (@pos_int < @neg_int).should == false + (@neg_int < @pos_frac).should == true + (@pos_frac < @neg_int).should == false + (@zero < @zero_pos).should == false + (@zero < @zero_neg).should == false + (@zero_neg < @zero_pos).should == false + (@zero_pos < @zero_neg).should == false + end + + it "properly handles infinity values" do + @values.each { |val| + (val < @infinity).should == true + (@infinity < val).should == false + (val < @infinity_neg).should == false + (@infinity_neg < val).should == true + } + (@infinity < @infinity).should == false + (@infinity_neg < @infinity_neg).should == false + (@infinity < @infinity_neg).should == false + (@infinity_neg < @infinity).should == true + end + + it "properly handles Float infinity values" do + @values.each { |val| + (val < @float_infinity).should == true + (@float_infinity < val).should == false + (val < @float_infinity_neg).should == false + (@float_infinity_neg < val).should == true + } + end + + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan < val).should == false + (val < @nan).should == false + } + end + + it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do + -> {@zero < nil }.should raise_error(ArgumentError) + -> {@infinity < nil }.should raise_error(ArgumentError) + -> {@infinity_neg < nil }.should raise_error(ArgumentError) + -> {@mixed < nil }.should raise_error(ArgumentError) + -> {@pos_int < nil }.should raise_error(ArgumentError) + -> {@neg_frac < nil }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/bigdecimal/lte_spec.rb b/spec/ruby/library/bigdecimal/lte_spec.rb new file mode 100644 index 0000000000..b92be04123 --- /dev/null +++ b/spec/ruby/library/bigdecimal/lte_spec.rb @@ -0,0 +1,100 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#<=" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def <=(other) + BigDecimal('123') <= other + end + end + + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY + + @nan = BigDecimal("NaN") + end + + it "returns true if a <= b" do + one = BigDecimal("1") + two = BigDecimal("2") + + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") + + (@zero <= one).should == true + (two <= @zero).should == false + + (frac_2 <= frac_1).should == true + (two <= two).should == true + (frac_1 <= frac_1).should == true + + (@neg_int <= @pos_int).should == true + (@pos_int <= @neg_int).should == false + (@neg_int <= @pos_frac).should == true + (@pos_frac <= @neg_int).should == false + (@zero <= @zero_pos).should == true + (@zero <= @zero_neg).should == true + (@zero_neg <= @zero_pos).should == true + (@zero_pos <= @zero_neg).should == true + end + + it "properly handles infinity values" do + @values.each { |val| + (val <= @infinity).should == true + (@infinity <= val).should == false + (val <= @infinity_neg).should == false + (@infinity_neg <= val).should == true + } + (@infinity <= @infinity).should == true + (@infinity_neg <= @infinity_neg).should == true + (@infinity <= @infinity_neg).should == false + (@infinity_neg <= @infinity).should == true + end + + it "properly handles Float infinity values" do + @values.each { |val| + (val <= @float_infinity).should == true + (@float_infinity <= val).should == false + (val <= @float_infinity_neg).should == false + (@float_infinity_neg <= val).should == true + } + end + + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan <= val).should == false + (val <= @nan).should == false + } + end + + it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do + -> {@zero <= nil }.should raise_error(ArgumentError) + -> {@infinity <= nil }.should raise_error(ArgumentError) + -> {@infinity_neg <= nil }.should raise_error(ArgumentError) + -> {@mixed <= nil }.should raise_error(ArgumentError) + -> {@pos_int <= nil }.should raise_error(ArgumentError) + -> {@neg_frac <= nil }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/bigdecimal/minus_spec.rb b/spec/ruby/library/bigdecimal/minus_spec.rb new file mode 100644 index 0000000000..bd3c19584b --- /dev/null +++ b/spec/ruby/library/bigdecimal/minus_spec.rb @@ -0,0 +1,66 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#-" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns a - b" do + (@two - @one).should == @one + (@one - @two).should == @one_minus + (@one - @one_minus).should == @two + (@frac_2 - @frac_1).should == BigDecimal("-0.1E-99999") + (@two - @two).should == @zero + (@frac_1 - @frac_1).should == @zero + (BigDecimal('1.23456789') - BigDecimal('1.2')).should == BigDecimal("0.03456789") + end + + it "returns NaN if NaN is involved" do + (@one - @nan).should.nan? + (@nan - @one).should.nan? + (@nan - @nan).should.nan? + (@nan - @infinity).should.nan? + (@nan - @infinity_minus).should.nan? + (@infinity - @nan).should.nan? + (@infinity_minus - @nan).should.nan? + end + + it "returns NaN both operands are infinite with the same sign" do + (@infinity - @infinity).should.nan? + (@infinity_minus - @infinity_minus).should.nan? + end + + it "returns Infinity or -Infinity if these are involved" do + (@infinity - @infinity_minus).should == @infinity + (@infinity_minus - @infinity).should == @infinity_minus + + (@infinity - @zero).should == @infinity + (@infinity - @frac_2).should == @infinity + (@infinity - @two).should == @infinity + (@infinity - @one_minus).should == @infinity + + (@zero - @infinity).should == @infinity_minus + (@frac_2 - @infinity).should == @infinity_minus + (@two - @infinity).should == @infinity_minus + (@one_minus - @infinity).should == @infinity_minus + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")]) + (@one - object).should == BigDecimal("-41") + end + end + +end diff --git a/spec/ruby/library/bigdecimal/mode_spec.rb b/spec/ruby/library/bigdecimal/mode_spec.rb new file mode 100644 index 0000000000..73fa1a118e --- /dev/null +++ b/spec/ruby/library/bigdecimal/mode_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal.mode" do + #the default value of BigDecimal exception constants is false + after :each do + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false) + end + + it "returns the appropriate value and continue the computation if the flag is false" do + BigDecimal("NaN").add(BigDecimal("1"),0).should.nan? + BigDecimal("0").add(BigDecimal("Infinity"),0).should == BigDecimal("Infinity") + BigDecimal("1").quo(BigDecimal("0")).should == BigDecimal("Infinity") + end + + it "returns Infinity when too big" do + BigDecimal("1E11111111111111111111").should == BigDecimal("Infinity") + (BigDecimal("1E1000000000000000000")**10).should == BigDecimal("Infinity") + end + + it "raise an exception if the flag is true" do + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) + -> { BigDecimal("NaN").add(BigDecimal("1"),0) }.should raise_error(FloatDomainError) + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) + -> { BigDecimal("0").add(BigDecimal("Infinity"),0) }.should raise_error(FloatDomainError) + BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) + -> { BigDecimal("1").quo(BigDecimal("0")) }.should raise_error(FloatDomainError) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) + -> { BigDecimal("1E11111111111111111111") }.should raise_error(FloatDomainError) + -> { (BigDecimal("1E1000000000000000000")**10) }.should raise_error(FloatDomainError) + end +end diff --git a/spec/ruby/library/bigdecimal/modulo_spec.rb b/spec/ruby/library/bigdecimal/modulo_spec.rb new file mode 100644 index 0000000000..035d31bd98 --- /dev/null +++ b/spec/ruby/library/bigdecimal/modulo_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'shared/modulo' + +describe "BigDecimal#%" do + it_behaves_like :bigdecimal_modulo, :% + it_behaves_like :bigdecimal_modulo_zerodivisionerror, :% +end + +describe "BigDecimal#modulo" do + it_behaves_like :bigdecimal_modulo, :modulo + it_behaves_like :bigdecimal_modulo_zerodivisionerror, :modulo +end diff --git a/spec/ruby/library/bigdecimal/mult_spec.rb b/spec/ruby/library/bigdecimal/mult_spec.rb new file mode 100644 index 0000000000..2353df9cb8 --- /dev/null +++ b/spec/ruby/library/bigdecimal/mult_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'shared/mult' +require 'bigdecimal' + +describe "BigDecimal#mult" do + it_behaves_like :bigdecimal_mult, :mult, [10] +end + +describe "BigDecimal#mult" do + before :each do + @one = BigDecimal "1" + @e3_minus = BigDecimal("3E-20001") + @e3_plus = BigDecimal("3E20001") + @e = BigDecimal "1.00000000000000000000123456789" + @tolerance = @e.sub @one, 1000 + @tolerance2 = BigDecimal "30001E-20005" + + end + + it "multiply self with other with (optional) precision" do + @e.mult(@one, 1).should be_close(@one, @tolerance) + @e3_minus.mult(@one, 1).should be_close(0, @tolerance2) + end +end diff --git a/spec/ruby/library/bigdecimal/multiply_spec.rb b/spec/ruby/library/bigdecimal/multiply_spec.rb new file mode 100644 index 0000000000..a8ce1da32e --- /dev/null +++ b/spec/ruby/library/bigdecimal/multiply_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require_relative 'shared/mult' +require 'bigdecimal' + +describe "BigDecimal#*" do + it_behaves_like :bigdecimal_mult, :*, [] +end + +describe "BigDecimal#*" do + before :each do + @three = BigDecimal("3") + @e3_minus = BigDecimal("3E-20001") + @e3_plus = BigDecimal("3E20001") + @e = BigDecimal("1.00000000000000000000123456789") + @one = BigDecimal("1") + end + + it "multiply self with other" do + (@one * @one).should == @one + (@e3_minus * @e3_plus).should == BigDecimal("9") + # Can't do this till we implement ** + # (@e3_minus * @e3_minus).should == @e3_minus ** 2 + # So let's rewrite it as: + (@e3_minus * @e3_minus).should == BigDecimal("9E-40002") + (@e * @one).should == @e + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) + (@e3_minus * object).should == BigDecimal("9") + end + end + + describe "with Rational" do + it "produces a BigDecimal" do + (@three * Rational(500, 2)).should == BigDecimal("0.75e3") + end + end +end diff --git a/spec/ruby/library/bigdecimal/nan_spec.rb b/spec/ruby/library/bigdecimal/nan_spec.rb new file mode 100644 index 0000000000..9eaf69b610 --- /dev/null +++ b/spec/ruby/library/bigdecimal/nan_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#nan?" do + + it "returns true if self is not a number" do + BigDecimal("NaN").should.nan? + end + + it "returns false if self is not a NaN" do + BigDecimal("Infinity").should_not.nan? + BigDecimal("-Infinity").should_not.nan? + BigDecimal("0").should_not.nan? + BigDecimal("+0").should_not.nan? + BigDecimal("-0").should_not.nan? + BigDecimal("2E40001").should_not.nan? + BigDecimal("3E-20001").should_not.nan? + BigDecimal("0E-200000000").should_not.nan? + BigDecimal("0E200000000000").should_not.nan? + BigDecimal("0.000000000000000000000000").should_not.nan? + end + +end diff --git a/spec/ruby/library/bigdecimal/nonzero_spec.rb b/spec/ruby/library/bigdecimal/nonzero_spec.rb new file mode 100644 index 0000000000..f43c4393cd --- /dev/null +++ b/spec/ruby/library/bigdecimal/nonzero_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#nonzero?" do + + it "returns self if self doesn't equal zero" do + # documentation says, it returns true. (04/10/08) + e2_plus = BigDecimal("2E40001") + e3_minus = BigDecimal("3E-20001") + infinity = BigDecimal("Infinity") + infinity_minus = BigDecimal("-Infinity") + nan = BigDecimal("NaN") + infinity.nonzero?.should equal(infinity) + infinity_minus.nonzero?.should equal(infinity_minus) + nan.nonzero?.should equal(nan) + e3_minus.nonzero?.should equal(e3_minus) + e2_plus.nonzero?.should equal(e2_plus) + end + + it "returns nil otherwise" do + # documentation states, it should return false. (04/10/08) + really_small_zero = BigDecimal("0E-200000000") + really_big_zero = BigDecimal("0E200000000000") + really_small_zero.nonzero?.should == nil + really_big_zero.nonzero?.should == nil + BigDecimal("0.000000000000000000000000").nonzero?.should == nil + end + +end diff --git a/spec/ruby/library/bigdecimal/plus_spec.rb b/spec/ruby/library/bigdecimal/plus_spec.rb new file mode 100644 index 0000000000..d1934841c8 --- /dev/null +++ b/spec/ruby/library/bigdecimal/plus_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#+" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @ten = BigDecimal("10") + @eleven = BigDecimal("11") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns a + b" do + (@two + @one).should == @three + (@one + @two).should == @three + (@one + @one_minus).should == @zero + (@zero + @one).should == @one + (@ten + @one).should == @eleven + (@frac_1 + @frac_2).should == BigDecimal("1.9E-99999") + (@frac_2 + @frac_1).should == BigDecimal("1.9E-99999") + (@frac_1 + @frac_1).should == BigDecimal("2E-99999") + end + + it "returns NaN if NaN is involved" do + (@one + @nan).should.nan? + (@nan + @one).should.nan? + end + + it "returns Infinity or -Infinity if these are involved" do + (@zero + @infinity).should == @infinity + (@frac_2 + @infinity).should == @infinity + (@two + @infinity_minus).should == @infinity_minus + end + + it "returns NaN if Infinity + (- Infinity)" do + (@infinity + @infinity_minus).should.nan? + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")]) + (@one + object).should == BigDecimal("43") + end + end +end diff --git a/spec/ruby/library/bigdecimal/power_spec.rb b/spec/ruby/library/bigdecimal/power_spec.rb new file mode 100644 index 0000000000..63a45a1887 --- /dev/null +++ b/spec/ruby/library/bigdecimal/power_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/power' + +describe "BigDecimal#power" do + it_behaves_like :bigdecimal_power, :power +end diff --git a/spec/ruby/library/bigdecimal/quo_spec.rb b/spec/ruby/library/bigdecimal/quo_spec.rb new file mode 100644 index 0000000000..65a4330303 --- /dev/null +++ b/spec/ruby/library/bigdecimal/quo_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'shared/quo' +require 'bigdecimal' + +describe "BigDecimal#quo" do + it_behaves_like :bigdecimal_quo, :quo, [] + + it "returns NaN if NaN is involved" do + BigDecimal("1").quo(BigDecimal("NaN")).should.nan? + BigDecimal("NaN").quo(BigDecimal("1")).should.nan? + end +end diff --git a/spec/ruby/library/bigdecimal/remainder_spec.rb b/spec/ruby/library/bigdecimal/remainder_spec.rb new file mode 100644 index 0000000000..0eb06f7ef1 --- /dev/null +++ b/spec/ruby/library/bigdecimal/remainder_spec.rb @@ -0,0 +1,96 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#remainder" do + + before :each do + @zero = BigDecimal("0") + @one = BigDecimal("1") + @three = BigDecimal("3") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "it equals modulo, if both values are of same sign" do + BigDecimal('1234567890123456789012345679').remainder(BigDecimal('1')).should == @zero + BigDecimal('123456789').remainder(BigDecimal('333333333333333333333333333E-50')).should == BigDecimal('0.12233333333333333333345679E-24') + + @mixed.remainder(@pos_frac).should == @mixed % @pos_frac + @pos_int.remainder(@pos_frac).should == @pos_int % @pos_frac + @neg_frac.remainder(@neg_int).should == @neg_frac % @neg_int + @neg_int.remainder(@neg_frac).should == @neg_int % @neg_frac + end + + it "means self-arg*(self/arg).truncate" do + @mixed.remainder(@neg_frac).should == @mixed - @neg_frac * (@mixed / @neg_frac).truncate + @pos_int.remainder(@neg_frac).should == @pos_int - @neg_frac * (@pos_int / @neg_frac).truncate + @neg_frac.remainder(@pos_int).should == @neg_frac - @pos_int * (@neg_frac / @pos_int).truncate + @neg_int.remainder(@pos_frac).should == @neg_int - @pos_frac * (@neg_int / @pos_frac).truncate + end + + version_is BigDecimal::VERSION, "3.3.0" do + it "raises ZeroDivisionError used with zero" do + -> { @mixed.remainder(@zero) }.should raise_error(ZeroDivisionError) + -> { @zero.remainder(@zero) }.should raise_error(ZeroDivisionError) + end + end + + it "returns zero if used on zero" do + @zero.remainder(@mixed).should == @zero + end + + it "returns NaN if NaN is involved" do + @nan.remainder(@nan).should.nan? + @nan.remainder(@one).should.nan? + @one.remainder(@nan).should.nan? + @infinity.remainder(@nan).should.nan? + @nan.remainder(@infinity).should.nan? + end + + version_is BigDecimal::VERSION, ""..."3.1.4" do #ruby_version_is ""..."3.3" do + it "returns NaN if Infinity is involved" do + @infinity.remainder(@infinity).should.nan? + @infinity.remainder(@one).should.nan? + @infinity.remainder(@mixed).should.nan? + @infinity.remainder(@one_minus).should.nan? + @infinity.remainder(@frac_1).should.nan? + @one.remainder(@infinity).should.nan? + + @infinity_minus.remainder(@infinity_minus).should.nan? + @infinity_minus.remainder(@one).should.nan? + @one.remainder(@infinity_minus).should.nan? + @frac_2.remainder(@infinity_minus).should.nan? + + @infinity.remainder(@infinity_minus).should.nan? + @infinity_minus.remainder(@infinity).should.nan? + end + end + + it "coerces arguments to BigDecimal if possible" do + @three.remainder(2).should == @one + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@three).and_return([@three, 2]) + @three.remainder(object).should == @one + end + end + + it "raises TypeError if the argument cannot be coerced to BigDecimal" do + -> { + @one.remainder('2') + }.should raise_error(TypeError) + end + +end diff --git a/spec/ruby/library/bigdecimal/round_spec.rb b/spec/ruby/library/bigdecimal/round_spec.rb new file mode 100644 index 0000000000..6a4d220417 --- /dev/null +++ b/spec/ruby/library/bigdecimal/round_spec.rb @@ -0,0 +1,234 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#round" do + before :each do + @one = BigDecimal("1") + @two = BigDecimal("2") + @three = BigDecimal("3") + + @neg_one = BigDecimal("-1") + @neg_two = BigDecimal("-2") + @neg_three = BigDecimal("-3") + + @p1_50 = BigDecimal("1.50") + @p1_51 = BigDecimal("1.51") + @p1_49 = BigDecimal("1.49") + @n1_50 = BigDecimal("-1.50") + @n1_51 = BigDecimal("-1.51") + @n1_49 = BigDecimal("-1.49") + + @p2_50 = BigDecimal("2.50") + @p2_51 = BigDecimal("2.51") + @p2_49 = BigDecimal("2.49") + @n2_50 = BigDecimal("-2.50") + @n2_51 = BigDecimal("-2.51") + @n2_49 = BigDecimal("-2.49") + end + + after :each do + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) + end + + it "uses default rounding method unless given" do + @p1_50.round(0).should == @two + @p1_51.round(0).should == @two + @p1_49.round(0).should == @one + @n1_50.round(0).should == @neg_two + @n1_51.round(0).should == @neg_two + @n1_49.round(0).should == @neg_one + + @p2_50.round(0).should == @three + @p2_51.round(0).should == @three + @p2_49.round(0).should == @two + @n2_50.round(0).should == @neg_three + @n2_51.round(0).should == @neg_three + @n2_49.round(0).should == @neg_two + + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN) + + @p1_50.round(0).should == @one + @p1_51.round(0).should == @one + @p1_49.round(0).should == @one + @n1_50.round(0).should == @neg_one + @n1_51.round(0).should == @neg_one + @n1_49.round(0).should == @neg_one + + @p2_50.round(0).should == @two + @p2_51.round(0).should == @two + @p2_49.round(0).should == @two + @n2_50.round(0).should == @neg_two + @n2_51.round(0).should == @neg_two + @n2_49.round(0).should == @neg_two + end + + ["BigDecimal::ROUND_UP", ":up"].each do |way| + describe way do + it "rounds values away from zero" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @two + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_two + + @p2_50.round(0, mode).should == @three + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @three + @n2_50.round(0, mode).should == @neg_three + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_three + end + end + end + + ["BigDecimal::ROUND_DOWN", ":down", ":truncate"].each do |way| + describe way do + it "rounds values towards zero" do + mode = eval(way) + + @p1_50.round(0, mode).should == @one + @p1_51.round(0, mode).should == @one + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_one + @n1_51.round(0, mode).should == @neg_one + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @two + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_two + @n2_49.round(0, mode).should == @neg_two + end + end + end + + ["BigDecimal::ROUND_HALF_UP", ":half_up", ":default"].each do |way| + describe way do + it "rounds values >= 5 up, otherwise down" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @three + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_three + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_two + end + end + end + + ["BigDecimal::ROUND_HALF_DOWN", ":half_down"].each do |way| + describe way do + it "rounds values > 5 up, otherwise down" do + mode = eval(way) + + @p1_50.round(0, mode).should == @one + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_one + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_two + end + end + end + + ["BigDecimal::ROUND_CEILING", ":ceiling", ":ceil"].each do |way| + describe way do + it "rounds values towards +infinity" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @two + @n1_50.round(0, mode).should == @neg_one + @n1_51.round(0, mode).should == @neg_one + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @three + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @three + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_two + @n2_49.round(0, mode).should == @neg_two + end + end + end + + ["BigDecimal::ROUND_FLOOR", ":floor"].each do |way| + describe way do + it "rounds values towards -infinity" do + mode = eval(way) + + @p1_50.round(0, mode).should == @one + @p1_51.round(0, mode).should == @one + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_two + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @two + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_three + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_three + end + end + end + + ["BigDecimal::ROUND_HALF_EVEN", ":half_even", ":banker"].each do |way| + describe way do + it "rounds values > 5 up, < 5 down and == 5 towards even neighbor" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_two + end + end + end + + it 'raise exception, if self is special value' do + -> { BigDecimal('NaN').round }.should raise_error(FloatDomainError) + -> { BigDecimal('Infinity').round }.should raise_error(FloatDomainError) + -> { BigDecimal('-Infinity').round }.should raise_error(FloatDomainError) + end + + it 'do not raise exception, if self is special value and precision is given' do + -> { BigDecimal('NaN').round(2) }.should_not raise_error(FloatDomainError) + -> { BigDecimal('Infinity').round(2) }.should_not raise_error(FloatDomainError) + -> { BigDecimal('-Infinity').round(2) }.should_not raise_error(FloatDomainError) + end + + it 'raise for a non-existent round mode' do + -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode (nonsense)") + end +end diff --git a/spec/ruby/library/bigdecimal/shared/clone.rb b/spec/ruby/library/bigdecimal/shared/clone.rb new file mode 100644 index 0000000000..935ef76e7e --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/clone.rb @@ -0,0 +1,13 @@ +require 'bigdecimal' + +describe :bigdecimal_clone, shared: true do + before :each do + @obj = BigDecimal("1.2345") + end + + it "returns self" do + copy = @obj.public_send(@method) + + copy.should equal(@obj) + end +end diff --git a/spec/ruby/library/bigdecimal/shared/eql.rb b/spec/ruby/library/bigdecimal/shared/eql.rb new file mode 100644 index 0000000000..8e3e388bab --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/eql.rb @@ -0,0 +1,61 @@ +require 'bigdecimal' + +describe :bigdecimal_eql, shared: true do + before :each do + @bg6543_21 = BigDecimal("6543.21") + @bg5667_19 = BigDecimal("5667.19") + @a = BigDecimal("1.0000000000000000000000000000000000000000005") + @b = BigDecimal("1.00000000000000000000000000000000000000000005") + @bigint = BigDecimal("1000.0") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + end + + it "tests for equality" do + @bg6543_21.send(@method, @bg6543_21).should == true + @a.send(@method, @a).should == true + @a.send(@method, @b).should == false + @bg6543_21.send(@method, @a).should == false + @bigint.send(@method, 1000).should == true + end + + it "returns false for NaN as it is never equal to any number" do + @nan.send(@method, @nan).should == false + @a.send(@method, @nan).should == false + @nan.send(@method, @a).should == false + @nan.send(@method, @infinity).should == false + @nan.send(@method, @infinity_minus).should == false + @infinity.send(@method, @nan).should == false + @infinity_minus.send(@method, @nan).should == false + end + + it "returns true for infinity values with the same sign" do + @infinity.send(@method, @infinity).should == true + @infinity.send(@method, BigDecimal("Infinity")).should == true + BigDecimal("Infinity").send(@method, @infinity).should == true + + @infinity_minus.send(@method, @infinity_minus).should == true + @infinity_minus.send(@method, BigDecimal("-Infinity")).should == true + BigDecimal("-Infinity").send(@method, @infinity_minus).should == true + end + + it "returns false for infinity values with different signs" do + @infinity.send(@method, @infinity_minus).should == false + @infinity_minus.send(@method, @infinity).should == false + end + + it "returns false when infinite value compared to finite one" do + @infinity.send(@method, @a).should == false + @infinity_minus.send(@method, @a).should == false + + @a.send(@method, @infinity).should == false + @a.send(@method, @infinity_minus).should == false + end + + it "returns false when compared objects that can not be coerced into BigDecimal" do + @infinity.send(@method, nil).should == false + @bigint.send(@method, nil).should == false + @nan.send(@method, nil).should == false + end +end diff --git a/spec/ruby/library/bigdecimal/shared/modulo.rb b/spec/ruby/library/bigdecimal/shared/modulo.rb new file mode 100644 index 0000000000..eeb030fd23 --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/modulo.rb @@ -0,0 +1,131 @@ +require 'bigdecimal' + +describe :bigdecimal_modulo, shared: true do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @mixed = BigDecimal("1.23456789") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-9999") + @frac_2 = BigDecimal("0.9E-9999") + end + + it "returns self modulo other" do + bd6543 = BigDecimal("6543.21") + bd5667 = BigDecimal("5667.19") + a = BigDecimal("1.0000000000000000000000000000000000000000005") + b = BigDecimal("1.00000000000000000000000000000000000000000005") + + bd6543.send(@method, 137).should == BigDecimal("104.21") + bd5667.send(@method, bignum_value).should == 5667.19 + bd6543.send(@method, BigDecimal("137.24")).should == BigDecimal("92.93") + bd6543.send(@method, 137).should be_close(6543.21.%(137), TOLERANCE) + bd6543.send(@method, 137).should == bd6543 % 137 + bd5667.send(@method, bignum_value).should be_close(5667.19.%(0xffffffff), TOLERANCE) + bd5667.send(@method, bignum_value).should == bd5667.%(0xffffffff) + bd6543.send(@method, 137.24).should be_close(6543.21.%(137.24), TOLERANCE) + a.send(@method, b).should == BigDecimal("0.45E-42") + @zero.send(@method, @one).should == @zero + @zero.send(@method, @one_minus).should == @zero + @two.send(@method, @one).should == @zero + @one.send(@method, @two).should == @one + @frac_1.send(@method, @one).should == @frac_1 + @frac_2.send(@method, @one).should == @frac_2 + @one_minus.send(@method, @one_minus).should == @zero + @one_minus.send(@method, @one).should == @zero + @one_minus.send(@method, @two).should == @one + @one.send(@method, -@two).should == -@one + + @one_minus.modulo(BigDecimal('0.9')).should == BigDecimal('0.8') + @one.modulo(BigDecimal('-0.9')).should == BigDecimal('-0.8') + + @one_minus.modulo(BigDecimal('0.8')).should == BigDecimal('0.6') + @one.modulo(BigDecimal('-0.8')).should == BigDecimal('-0.6') + + @one_minus.modulo(BigDecimal('0.6')).should == BigDecimal('0.2') + @one.modulo(BigDecimal('-0.6')).should == BigDecimal('-0.2') + + @one_minus.modulo(BigDecimal('0.5')).should == @zero + @one.modulo(BigDecimal('-0.5')).should == @zero + @one_minus.modulo(BigDecimal('-0.5')).should == @zero + + @one_minus.modulo(BigDecimal('0.4')).should == BigDecimal('0.2') + @one.modulo(BigDecimal('-0.4')).should == BigDecimal('-0.2') + + @one_minus.modulo(BigDecimal('0.3')).should == BigDecimal('0.2') + @one_minus.modulo(BigDecimal('0.2')).should == @zero + end + + it "returns a [Float value] when the argument is Float" do + @two.send(@method, 2.0).should == 0.0 + @one.send(@method, 2.0).should == 1.0 + res = @two.send(@method, 5.0) + res.kind_of?(BigDecimal).should == true + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + bd6543 = BigDecimal("6543.21") + object = mock("Object") + object.should_receive(:coerce).with(bd6543).and_return([bd6543, 137]) + bd6543.send(@method, object, *@object).should == BigDecimal("104.21") + end + end + + it "returns NaN if NaN is involved" do + @nan.send(@method, @nan).should.nan? + @nan.send(@method, @one).should.nan? + @one.send(@method, @nan).should.nan? + @infinity.send(@method, @nan).should.nan? + @nan.send(@method, @infinity).should.nan? + end + + it "returns NaN if the dividend is Infinity" do + @infinity.send(@method, @infinity).should.nan? + @infinity.send(@method, @one).should.nan? + @infinity.send(@method, @mixed).should.nan? + @infinity.send(@method, @one_minus).should.nan? + @infinity.send(@method, @frac_1).should.nan? + + @infinity_minus.send(@method, @infinity_minus).should.nan? + @infinity_minus.send(@method, @one).should.nan? + + @infinity.send(@method, @infinity_minus).should.nan? + @infinity_minus.send(@method, @infinity).should.nan? + end + + version_is BigDecimal::VERSION, "3.3.0" do + it "returns the dividend if the divisor is Infinity and signs are same" do + @one.send(@method, @infinity).should == @one + (-@frac_2).send(@method, @infinity_minus).should == -@frac_2 + end + + it "returns the divisor if the divisor is Infinity and signs are different" do + (-@one).send(@method, @infinity).should == @infinity + @frac_2.send(@method, @infinity_minus).should == @infinity_minus + end + end + + it "raises TypeError if the argument cannot be coerced to BigDecimal" do + -> { + @one.send(@method, '2') + }.should raise_error(TypeError) + end +end + +describe :bigdecimal_modulo_zerodivisionerror, shared: true do + it "raises ZeroDivisionError if other is zero" do + bd5667 = BigDecimal("5667.19") + + -> { bd5667.send(@method, 0) }.should raise_error(ZeroDivisionError) + -> { bd5667.send(@method, BigDecimal("0")) }.should raise_error(ZeroDivisionError) + -> { @zero.send(@method, @zero) }.should raise_error(ZeroDivisionError) + end +end diff --git a/spec/ruby/library/bigdecimal/shared/mult.rb b/spec/ruby/library/bigdecimal/shared/mult.rb new file mode 100644 index 0000000000..c613df04c4 --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/mult.rb @@ -0,0 +1,97 @@ +require 'bigdecimal' + +describe :bigdecimal_mult, shared: true do + before :each do + @zero = BigDecimal "0" + @zero_pos = BigDecimal "+0" + @zero_neg = BigDecimal "-0" + + @one = BigDecimal "1" + @mixed = BigDecimal "1.23456789" + @pos_int = BigDecimal "2E5555" + @neg_int = BigDecimal "-2E5555" + @pos_frac = BigDecimal "2E-9999" + @neg_frac = BigDecimal "-2E-9999" + @nan = BigDecimal "NaN" + @infinity = BigDecimal "Infinity" + @infinity_minus = BigDecimal "-Infinity" + @one_minus = BigDecimal "-1" + @frac_1 = BigDecimal "1E-99999" + @frac_2 = BigDecimal "0.9E-99999" + + @e3_minus = BigDecimal "3E-20001" + @e = BigDecimal "1.00000000000000000000123456789" + @tolerance = @e.sub @one, 1000 + @tolerance2 = BigDecimal "30001E-20005" + + @special_vals = [@infinity, @infinity_minus, @nan] + @regular_vals = [ @one, @mixed, @pos_int, @neg_int, + @pos_frac, @neg_frac, @one_minus, + @frac_1, @frac_2 + ] + @zeroes = [@zero, @zero_pos, @zero_neg] + end + + it "returns zero of appropriate sign if self or argument is zero" do + @zero.send(@method, @zero, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero_neg.send(@method, @zero_neg, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero.send(@method, @zero_neg, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + @zero_neg.send(@method, @zero, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + + @one.send(@method, @zero, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @one.send(@method, @zero_neg, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + + @zero.send(@method, @one, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero.send(@method, @one_minus, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + @zero_neg.send(@method, @one_minus, *@object).sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero_neg.send(@method, @one, *@object).sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + end + + it "returns NaN if NaN is involved" do + values = @regular_vals + @zeroes + + values.each do |val| + @nan.send(@method, val, *@object).should.nan? + val.send(@method, @nan, *@object).should.nan? + end + end + + it "returns zero if self or argument is zero" do + values = @regular_vals + @zeroes + + values.each do |val| + @zeroes.each do |zero| + zero.send(@method, val, *@object).should == 0 + zero.send(@method, val, *@object).should.zero? + val.send(@method, zero, *@object).should == 0 + val.send(@method, zero, *@object).should.zero? + end + end + end + + it "returns infinite value if self or argument is infinite" do + values = @regular_vals + infs = [@infinity, @infinity_minus] + + values.each do |val| + infs.each do |inf| + inf.send(@method, val, *@object).should_not.finite? + val.send(@method, inf, *@object).should_not.finite? + end + end + + @infinity.send(@method, @infinity, *@object).infinite?.should == 1 + @infinity_minus.send(@method, @infinity_minus, *@object).infinite?.should == 1 + @infinity.send(@method, @infinity_minus, *@object).infinite?.should == -1 + @infinity_minus.send(@method, @infinity, *@object).infinite?.should == -1 + @infinity.send(@method, @one, *@object).infinite?.should == 1 + @infinity_minus.send(@method, @one, *@object).infinite?.should == -1 + end + + it "returns NaN if the result is undefined" do + @zero.send(@method, @infinity, *@object).should.nan? + @zero.send(@method, @infinity_minus, *@object).should.nan? + @infinity.send(@method, @zero, *@object).should.nan? + @infinity_minus.send(@method, @zero, *@object).should.nan? + end +end diff --git a/spec/ruby/library/bigdecimal/shared/power.rb b/spec/ruby/library/bigdecimal/shared/power.rb new file mode 100644 index 0000000000..6dafb638e2 --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/power.rb @@ -0,0 +1,72 @@ +require 'bigdecimal' + +describe :bigdecimal_power, shared: true do + it "powers of self" do + e3_minus = BigDecimal("3E-20001") + e3_minus_power_2 = BigDecimal("9E-40002") + e3_plus = BigDecimal("3E20001") + e2_plus = BigDecimal("2E40001") + e5_minus = BigDecimal("5E-40002") + e = BigDecimal("1.00000000000000000000123456789") + one = BigDecimal("1") + ten = BigDecimal("10") + # Accuracy is at least ndigits(== 30) + DOUBLE_FIG(== 16) + tolerance = BigDecimal("1E-46") + ten_powers = BigDecimal("1E10000") + pi = BigDecimal("3.14159265358979") + e3_minus.send(@method, 2).should == e3_minus_power_2 + e3_plus.send(@method, 0).should == 1 + e3_minus.send(@method, 1).should == e3_minus + e2_plus.send(@method, -1).should == e5_minus + e2_plus.send(@method, -1).should == e5_minus.power(1) + (e2_plus.send(@method, -1) * e5_minus.send(@method, -1)).should == 1 + e.send(@method, 2).should == e * e + e.send(@method, -1).should be_close(one.div(e, 120), tolerance) + ten.send(@method, 10000).should == ten_powers + pi.send(@method, 10).should be_close(Math::PI ** 10, TOLERANCE) + end + + it "powers of 1 equal 1" do + one = BigDecimal("1") + one.send(@method, 0).should == 1 + one.send(@method, 1).should == 1 + one.send(@method, 10).should == 1 + one.send(@method, -10).should == 1 + end + + it "0 to power of 0 is 1" do + zero = BigDecimal("0") + zero.send(@method, 0).should == 1 + end + + it "0 to powers < 0 is Infinity" do + zero = BigDecimal("0") + infinity = BigDecimal("Infinity") + zero.send(@method, -10).should == infinity + zero.send(@method, -1).should == infinity + end + + it "other powers of 0 are 0" do + zero = BigDecimal("0") + zero.send(@method, 1).should == 0 + zero.send(@method, 10).should == 0 + end + + it "returns NaN if self is NaN" do + BigDecimal("NaN").send(@method, -5).should.nan? + BigDecimal("NaN").send(@method, 5).should.nan? + end + + it "returns 0.0 if self is infinite and argument is negative" do + BigDecimal("Infinity").send(@method, -5).should == 0 + BigDecimal("-Infinity").send(@method, -5).should == 0 + end + + it "returns infinite if self is infinite and argument is positive" do + infinity = BigDecimal("Infinity") + BigDecimal("Infinity").send(@method, 4).should == infinity + BigDecimal("-Infinity").send(@method, 4).should == infinity + BigDecimal("Infinity").send(@method, 5).should == infinity + BigDecimal("-Infinity").send(@method, 5).should == -infinity + end +end diff --git a/spec/ruby/library/bigdecimal/shared/quo.rb b/spec/ruby/library/bigdecimal/shared/quo.rb new file mode 100644 index 0000000000..18ff2fe9a5 --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/quo.rb @@ -0,0 +1,68 @@ +require 'bigdecimal' + +describe :bigdecimal_quo, shared: true do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_plus = BigDecimal("+0") + @zero_minus = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @eleven = BigDecimal("11") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns a / b" do + @two.send(@method, @one, *@object).should == @two + @one.send(@method, @two, *@object).should == BigDecimal("0.5") + @eleven.send(@method, @three, *@object).should be_close(@three + (@two / @three), TOLERANCE) + @one.send(@method, @one_minus, *@object).should == @one_minus + @one_minus.send(@method, @one_minus, *@object).should == @one + @frac_2.send(@method, @frac_1, *@object).should == BigDecimal("0.9") + @frac_1.send(@method, @frac_1, *@object).should == @one + @one.send(@method, BigDecimal('-2E5555'), *@object).should == BigDecimal('-0.5E-5555') + @one.send(@method, BigDecimal('2E-5555'), *@object).should == BigDecimal('0.5E5555') + end + + describe "with Object" do + it "tries to coerce the other operand to self" do + skip if @method == :div + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, @two]) + @one.send(@method, object, *@object).should == BigDecimal("0.5") + end + end + + it "returns 0 if divided by Infinity" do + @zero.send(@method, @infinity, *@object).should == 0 + @frac_2.send(@method, @infinity, *@object).should == 0 + end + + it "returns (+|-) Infinity if (+|-) Infinity divided by one" do + @infinity_minus.send(@method, @one, *@object).should == @infinity_minus + @infinity.send(@method, @one, *@object).should == @infinity + @infinity_minus.send(@method, @one_minus, *@object).should == @infinity + end + + it "returns NaN if Infinity / ((+|-) Infinity)" do + @infinity.send(@method, @infinity_minus, *@object).should.nan? + @infinity_minus.send(@method, @infinity, *@object).should.nan? + end + + it "returns (+|-) Infinity if divided by zero" do + @one.send(@method, @zero, *@object).should == @infinity + @one.send(@method, @zero_plus, *@object).should == @infinity + @one.send(@method, @zero_minus, *@object).should == @infinity_minus + end + + it "returns NaN if zero is divided by zero" do + @zero.send(@method, @zero, *@object).should.nan? + @zero_minus.send(@method, @zero_plus, *@object).should.nan? + @zero_plus.send(@method, @zero_minus, *@object).should.nan? + end +end diff --git a/spec/ruby/library/bigdecimal/shared/to_int.rb b/spec/ruby/library/bigdecimal/shared/to_int.rb new file mode 100644 index 0000000000..44b6a3c7b2 --- /dev/null +++ b/spec/ruby/library/bigdecimal/shared/to_int.rb @@ -0,0 +1,16 @@ +require 'bigdecimal' + +describe :bigdecimal_to_int, shared: true do + it "raises FloatDomainError if BigDecimal is infinity or NaN" do + -> { BigDecimal("Infinity").send(@method) }.should raise_error(FloatDomainError) + -> { BigDecimal("NaN").send(@method) }.should raise_error(FloatDomainError) + end + + it "returns Integer otherwise" do + BigDecimal("3E-20001").send(@method).should == 0 + BigDecimal("2E4000").send(@method).should == 2 * 10 ** 4000 + BigDecimal("2").send(@method).should == 2 + BigDecimal("2E10").send(@method).should == 20000000000 + BigDecimal("3.14159").send(@method).should == 3 + end +end diff --git a/spec/ruby/library/bigdecimal/sign_spec.rb b/spec/ruby/library/bigdecimal/sign_spec.rb new file mode 100644 index 0000000000..ae2c28e9fd --- /dev/null +++ b/spec/ruby/library/bigdecimal/sign_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#sign" do + + it "defines several constants for signs" do + # are these really correct? + BigDecimal::SIGN_POSITIVE_INFINITE.should == 3 + BigDecimal::SIGN_NEGATIVE_INFINITE.should == -3 + BigDecimal::SIGN_POSITIVE_ZERO.should == 1 + BigDecimal::SIGN_NEGATIVE_ZERO.should == -1 + BigDecimal::SIGN_POSITIVE_FINITE.should == 2 + BigDecimal::SIGN_NEGATIVE_FINITE.should == -2 + end + + it "returns positive value if BigDecimal greater than 0" do + BigDecimal("1").sign.should == BigDecimal::SIGN_POSITIVE_FINITE + BigDecimal("1E-20000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE + BigDecimal("1E200000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE + BigDecimal("Infinity").sign.should == BigDecimal::SIGN_POSITIVE_INFINITE + end + + it "returns negative value if BigDecimal less than 0" do + BigDecimal("-1").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE + BigDecimal("-1E-9990000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE + BigDecimal("-1E20000000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE + BigDecimal("-Infinity").sign.should == BigDecimal::SIGN_NEGATIVE_INFINITE + end + + it "returns positive zero if BigDecimal equals positive zero" do + BigDecimal("0").sign.should == BigDecimal::SIGN_POSITIVE_ZERO + BigDecimal("0E-200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO + BigDecimal("0E200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO + end + + it "returns negative zero if BigDecimal equals negative zero" do + BigDecimal("-0").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + BigDecimal("-0E-200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + BigDecimal("-0E200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + end + + it "returns BigDecimal::SIGN_NaN if BigDecimal is NaN" do + BigDecimal("NaN").sign.should == BigDecimal::SIGN_NaN + end + +end diff --git a/spec/ruby/library/bigdecimal/split_spec.rb b/spec/ruby/library/bigdecimal/split_spec.rb new file mode 100644 index 0000000000..53b1f649d9 --- /dev/null +++ b/spec/ruby/library/bigdecimal/split_spec.rb @@ -0,0 +1,86 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#split" do + + before :each do + @arr = BigDecimal("0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split + @arr_neg = BigDecimal("-0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split + @digits = "922337203685477580810101333333333333333333333333333" + @arr_big = BigDecimal("00#{@digits}000").split + @arr_big_neg = BigDecimal("-00#{@digits}000").split + @huge = BigDecimal('100000000000000000000000000000000000000000001E90000000').split + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero = BigDecimal("0") + @zero_neg = BigDecimal("-0") + end + + it "splits BigDecimal in an array with four values" do + @arr.size.should == 4 + end + + it "first value: 1 for numbers > 0" do + @arr[0].should == 1 + @arr_big[0].should == 1 + @zero.split[0].should == 1 + @huge[0].should == 1 + BigDecimal("+0").split[0].should == 1 + BigDecimal("1E400").split[0].should == 1 + @infinity.split[0].should == 1 + end + + it "first value: -1 for numbers < 0" do + @arr_neg[0].should == -1 + @arr_big_neg[0].should == -1 + @zero_neg.split[0].should == -1 + BigDecimal("-1E400").split[0].should == -1 + @infinity_neg.split[0].should == -1 + end + + it "first value: 0 if BigDecimal is NaN" do + BigDecimal("NaN").split[0].should == 0 + end + + it "second value: a string with the significant digits" do + string = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" + @arr[1].should == string + @arr_big[1].should == @digits + @arr_big_neg[1].should == @digits + @huge[1].should == "100000000000000000000000000000000000000000001" + @infinity.split[1].should == @infinity.to_s + @nan.split[1].should == @nan.to_s + @infinity_neg.split[1].should == @infinity.to_s + @zero.split[1].should == "0" + BigDecimal("-0").split[1].should == "0" + end + + it "third value: the base (currently always ten)" do + @arr[2].should == 10 + @arr_neg[2].should == 10 + @arr_big[2].should == 10 + @arr_big_neg[2].should == 10 + @huge[2].should == 10 + @infinity.split[2].should == 10 + @nan.split[2].should == 10 + @infinity_neg.split[2].should == 10 + @zero.split[2].should == 10 + @zero_neg.split[2].should == 10 + end + + it "fourth value: the exponent" do + @arr[3].should == 1 + @arr_neg[3].should == 1 + @arr_big[3].should == 54 + @arr_big_neg[3].should == 54 + @huge[3].should == 90000045 + @infinity.split[3].should == 0 + @nan.split[3].should == 0 + @infinity_neg.split[3].should == 0 + @zero.split[3].should == 0 + @zero_neg.split[3].should == 0 + end + +end diff --git a/spec/ruby/library/bigdecimal/sqrt_spec.rb b/spec/ruby/library/bigdecimal/sqrt_spec.rb new file mode 100644 index 0000000000..42cf4545cb --- /dev/null +++ b/spec/ruby/library/bigdecimal/sqrt_spec.rb @@ -0,0 +1,114 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'bigdecimal' + +describe "BigDecimal#sqrt" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2.0") + @three = BigDecimal("3.0") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns square root of 2 with desired precision" do + string = "1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157" + (1..99).each { |idx| + @two.sqrt(idx).should be_close(BigDecimal(string), BigDecimal("1E-#{idx-1}")) + } + end + + it "returns square root of 3 with desired precision" do + sqrt_3 = "1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248575" + (1..99).each { |idx| + @three.sqrt(idx).should be_close(BigDecimal(sqrt_3), BigDecimal("1E-#{idx-1}")) + } + end + + it "returns square root of 121 with desired precision" do + BigDecimal('121').sqrt(5).should be_close(11, 0.00001) + end + + platform_is_not c_long_size: 32 do # fails on i686 + it "returns square root of 0.9E-99999 with desired precision" do + @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i + end + end + + it "raises ArgumentError when no argument is given" do + -> { + @one.sqrt + }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if a negative number is given" do + -> { + @one.sqrt(-1) + }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if 2 arguments are given" do + -> { + @one.sqrt(1, 1) + }.should raise_error(ArgumentError) + end + + it "raises TypeError if nil is given" do + -> { + @one.sqrt(nil) + }.should raise_error(TypeError) + end + + it "raises TypeError if a string is given" do + -> { + @one.sqrt("stuff") + }.should raise_error(TypeError) + end + + it "raises TypeError if a plain Object is given" do + -> { + @one.sqrt(Object.new) + }.should raise_error(TypeError) + end + + it "returns 1 if precision is 0 or 1" do + @one.sqrt(1).should == 1 + @one.sqrt(0).should == 1 + end + + it "raises FloatDomainError on negative values" do + -> { + BigDecimal('-1').sqrt(10) + }.should raise_error(FloatDomainError) + end + + it "returns positive infinity for infinity" do + @infinity.sqrt(1).should == @infinity + end + + it "raises FloatDomainError for negative infinity" do + -> { + @infinity_minus.sqrt(1) + }.should raise_error(FloatDomainError) + end + + it "raises FloatDomainError for NaN" do + -> { + @nan.sqrt(1) + }.should raise_error(FloatDomainError) + end + + it "returns 0 for 0, +0.0 and -0.0" do + @zero.sqrt(1).should == 0 + @zero_pos.sqrt(1).should == 0 + @zero_neg.sqrt(1).should == 0 + end + +end diff --git a/spec/ruby/library/bigdecimal/sub_spec.rb b/spec/ruby/library/bigdecimal/sub_spec.rb new file mode 100644 index 0000000000..3b62a0c794 --- /dev/null +++ b/spec/ruby/library/bigdecimal/sub_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#sub" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @frac_3 = BigDecimal("12345E10") + @frac_4 = BigDecimal("98765E10") + end + + it "returns a - b with given precision" do + # documentation states, that precision is optional + # but implementation raises ArgumentError if not given. + + @two.sub(@one, 1).should == @one + @one.sub(@two, 1).should == @one_minus + @one.sub(@one_minus, 1).should == @two + @frac_2.sub(@frac_1, 1000000).should == BigDecimal("-0.1E-99999") + @frac_2.sub(@frac_1, 1).should == BigDecimal("-0.1E-99999") + # the above two examples puzzle me. + in_arow_one = BigDecimal("1.23456789") + in_arow_two = BigDecimal("1.2345678") + in_arow_one.sub(in_arow_two, 10).should == BigDecimal("0.9E-7") + @two.sub(@two,1).should == @zero + @frac_1.sub(@frac_1, 1000000).should == @zero + end + + describe "with Rational" do + it "produces a BigDecimal" do + (@three - Rational(500, 2)).should == BigDecimal('-0.247e3') + end + end + + it "returns NaN if NaN is involved" do + @one.sub(@nan, 1).should.nan? + @nan.sub(@one, 1).should.nan? + end + + it "returns NaN if both values are infinite with the same signs" do + @infinity.sub(@infinity, 1).should.nan? + @infinity_minus.sub(@infinity_minus, 1).should.nan? + end + + it "returns Infinity or -Infinity if these are involved" do + @infinity.sub(@infinity_minus, 1).should == @infinity + @infinity_minus.sub(@infinity, 1).should == @infinity_minus + @zero.sub(@infinity, 1).should == @infinity_minus + @frac_2.sub( @infinity, 1).should == @infinity_minus + @two.sub(@infinity, 1).should == @infinity_minus + end + +end diff --git a/spec/ruby/library/bigdecimal/to_d_spec.rb b/spec/ruby/library/bigdecimal/to_d_spec.rb new file mode 100644 index 0000000000..50aea99bf7 --- /dev/null +++ b/spec/ruby/library/bigdecimal/to_d_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'bigdecimal' +require 'bigdecimal/util' + +describe "Float#to_d" do + it "returns appropriate BigDecimal zero for signed zero" do + -0.0.to_d.sign.should == -1 + 0.0.to_d.sign.should == 1 + end +end diff --git a/spec/ruby/library/bigdecimal/to_f_spec.rb b/spec/ruby/library/bigdecimal/to_f_spec.rb new file mode 100644 index 0000000000..84d4d49de2 --- /dev/null +++ b/spec/ruby/library/bigdecimal/to_f_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#to_f" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @vals = [@one, @zero, @two, @three, @frac_1, @frac_2] + @spec_vals = [@zero_pos, @zero_neg, @nan, @infinity, @infinity_minus] + end + + it "returns number of type float" do + BigDecimal("3.14159").to_f.should be_kind_of(Float) + @vals.each { |val| val.to_f.should be_kind_of(Float) } + @spec_vals.each { |val| val.to_f.should be_kind_of(Float) } + end + + it "rounds correctly to Float precision" do + bigdec = BigDecimal("3.141592653589793238462643383279502884197169399375") + bigdec.to_f.should be_close(3.14159265358979, TOLERANCE) + @one.to_f.should == 1.0 + @two.to_f.should == 2.0 + @three.to_f.should be_close(3.0, TOLERANCE) + @one_minus.to_f.should == -1.0 + + # regression test for [ruby-talk:338957] + BigDecimal("10.03").to_f.should == 10.03 + end + + it "properly handles special values" do + @zero.to_f.should == 0 + @zero.to_f.to_s.should == "0.0" + + @nan.to_f.should.nan? + + @infinity.to_f.infinite?.should == 1 + @infinity_minus.to_f.infinite?.should == -1 + end + + it "remembers negative zero when converted to float" do + @zero_neg.to_f.should == 0 + @zero_neg.to_f.to_s.should == "-0.0" + end +end diff --git a/spec/ruby/library/bigdecimal/to_i_spec.rb b/spec/ruby/library/bigdecimal/to_i_spec.rb new file mode 100644 index 0000000000..e5e65c562e --- /dev/null +++ b/spec/ruby/library/bigdecimal/to_i_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/to_int' +require 'bigdecimal' + +describe "BigDecimal#to_i" do + it_behaves_like :bigdecimal_to_int, :to_i +end diff --git a/spec/ruby/library/bigdecimal/to_int_spec.rb b/spec/ruby/library/bigdecimal/to_int_spec.rb new file mode 100644 index 0000000000..4df6749845 --- /dev/null +++ b/spec/ruby/library/bigdecimal/to_int_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'shared/to_int' +require 'bigdecimal' + + +describe "BigDecimal#to_int" do + it_behaves_like :bigdecimal_to_int, :to_int +end diff --git a/spec/ruby/library/bigdecimal/to_r_spec.rb b/spec/ruby/library/bigdecimal/to_r_spec.rb new file mode 100644 index 0000000000..c350beff08 --- /dev/null +++ b/spec/ruby/library/bigdecimal/to_r_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#to_r" do + + it "returns a Rational" do + BigDecimal("3.14159").to_r.should be_kind_of(Rational) + end + + it "returns a Rational with bignum values" do + r = BigDecimal("3.141592653589793238462643").to_r + r.numerator.should eql(3141592653589793238462643) + r.denominator.should eql(1000000000000000000000000) + end + + it "returns a Rational from a BigDecimal with an exponent" do + r = BigDecimal("1E2").to_r + r.numerator.should eql(100) + r.denominator.should eql(1) + end + + it "returns a Rational from a negative BigDecimal with an exponent" do + r = BigDecimal("-1E2").to_r + r.numerator.should eql(-100) + r.denominator.should eql(1) + end + +end diff --git a/spec/ruby/library/bigdecimal/to_s_spec.rb b/spec/ruby/library/bigdecimal/to_s_spec.rb new file mode 100644 index 0000000000..ba9f960eb3 --- /dev/null +++ b/spec/ruby/library/bigdecimal/to_s_spec.rb @@ -0,0 +1,100 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#to_s" do + + before :each do + @bigdec_str = "3.14159265358979323846264338327950288419716939937" + @bigneg_str = "-3.1415926535897932384626433832795028841971693993" + @bigdec = BigDecimal(@bigdec_str) + @bigneg = BigDecimal(@bigneg_str) + @internal = Encoding.default_internal + end + + after :each do + Encoding.default_internal = @internal + end + + it "return type is of class String" do + @bigdec.to_s.kind_of?(String).should == true + @bigneg.to_s.kind_of?(String).should == true + end + + it "the default format looks like 0.xxxxenn" do + @bigdec.to_s.should =~ /^0\.[0-9]*e[0-9]*$/ + end + + it "does not add an exponent for zero values" do + BigDecimal("0").to_s.should == "0.0" + BigDecimal("+0").to_s.should == "0.0" + BigDecimal("-0").to_s.should == "-0.0" + end + + it "takes an optional argument" do + -> {@bigdec.to_s("F")}.should_not raise_error() + end + + it "starts with + if + is supplied and value is positive" do + @bigdec.to_s("+").should =~ /^\+.*/ + @bigneg.to_s("+").should_not =~ /^\+.*/ + end + + it "inserts a space every n chars to fraction part, if integer n is supplied" do + re =\ + /\A0\.314 159 265 358 979 323 846 264 338 327 950 288 419 716 939 937E1\z/i + @bigdec.to_s(3).should =~ re + + str1 = '-123.45678 90123 45678 9' + BigDecimal("-123.45678901234567890").to_s('5F').should == str1 + # trailing zeroes removed + BigDecimal("1.00000000000").to_s('1F').should == "1.0" + # 0 is treated as no spaces + BigDecimal("1.2345").to_s('0F').should == "1.2345" + end + + version_is BigDecimal::VERSION, "3.1.5" do #ruby_version_is '3.3' do + it "inserts a space every n chars to integer part, if integer n is supplied" do + BigDecimal('1000010').to_s('5F').should == "10 00010.0" + end + end + + it "can return a leading space for values > 0" do + @bigdec.to_s(" F").should =~ /\ .*/ + @bigneg.to_s(" F").should_not =~ /\ .*/ + end + + it "removes trailing spaces in floating point notation" do + BigDecimal('-123.45678901234567890').to_s('F').should == "-123.4567890123456789" + BigDecimal('1.2500').to_s('F').should == "1.25" + BigDecimal('0000.00000').to_s('F').should == "0.0" + BigDecimal('-00.000010000').to_s('F').should == "-0.00001" + BigDecimal("5.00000E-2").to_s("F").should == "0.05" + + BigDecimal("500000").to_s("F").should == "500000.0" + BigDecimal("5E2").to_s("F").should == "500.0" + BigDecimal("-5E100").to_s("F").should == "-5" + "0" * 100 + ".0" + end + + it "can use engineering notation" do + @bigdec.to_s("E").should =~ /^0\.[0-9]*E[0-9]*$/i + end + + it "can use conventional floating point notation" do + %w[f F].each do |format_char| + @bigdec.to_s(format_char).should == @bigdec_str + @bigneg.to_s(format_char).should == @bigneg_str + str2 = "+123.45678901 23456789" + BigDecimal('123.45678901234567890').to_s("+8#{format_char}").should == str2 + end + end + + it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do + Encoding.default_internal = nil + BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) + end + + it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do + Encoding.default_internal = Encoding::IBM437 + BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) + end +end diff --git a/spec/ruby/library/bigdecimal/truncate_spec.rb b/spec/ruby/library/bigdecimal/truncate_spec.rb new file mode 100644 index 0000000000..0ae0421b30 --- /dev/null +++ b/spec/ruby/library/bigdecimal/truncate_spec.rb @@ -0,0 +1,81 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#truncate" do + + before :each do + @arr = ['3.14159', '8.7', "0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1"] + @big = BigDecimal("123456.789") + @nan = BigDecimal('NaN') + @infinity = BigDecimal('Infinity') + @infinity_negative = BigDecimal('-Infinity') + end + + it "returns value of type Integer." do + @arr.each do |x| + BigDecimal(x).truncate.kind_of?(Integer).should == true + end + end + + it "returns the integer part as a BigDecimal if no precision given" do + BigDecimal(@arr[0]).truncate.should == 3 + BigDecimal(@arr[1]).truncate.should == 8 + BigDecimal(@arr[2]).truncate.should == 3 + BigDecimal('0').truncate.should == 0 + BigDecimal('0.1').truncate.should == 0 + BigDecimal('-0.1').truncate.should == 0 + BigDecimal('1.5').truncate.should == 1 + BigDecimal('-1.5').truncate.should == -1 + BigDecimal('1E10').truncate.should == BigDecimal('1E10') + BigDecimal('-1E10').truncate.should == BigDecimal('-1E10') + BigDecimal('1.8888E10').truncate.should == BigDecimal('1.8888E10') + BigDecimal('-1E-1').truncate.should == 0 + end + + it "returns value of given precision otherwise" do + BigDecimal('-1.55').truncate(1).should == BigDecimal('-1.5') + BigDecimal('1.55').truncate(1).should == BigDecimal('1.5') + BigDecimal(@arr[0]).truncate(2).should == BigDecimal("3.14") + BigDecimal('123.456').truncate(2).should == BigDecimal("123.45") + BigDecimal('123.456789').truncate(4).should == BigDecimal("123.4567") + BigDecimal('0.456789').truncate(10).should == BigDecimal("0.456789") + BigDecimal('-1E-1').truncate(1).should == BigDecimal('-0.1') + BigDecimal('-1E-1').truncate(2).should == BigDecimal('-0.1E0') + BigDecimal('-1E-1').truncate.should == BigDecimal('0') + BigDecimal('-1E-1').truncate(0).should == BigDecimal('0') + BigDecimal('-1E-1').truncate(-1).should == BigDecimal('0') + BigDecimal('-1E-1').truncate(-2).should == BigDecimal('0') + + BigDecimal(@arr[1]).truncate(1).should == BigDecimal("8.7") + BigDecimal(@arr[2]).truncate(100).should == BigDecimal(\ + "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679") + end + + it "sets n digits left of the decimal point to 0, if given n < 0" do + @big.truncate(-1).should == BigDecimal("123450.0") + @big.truncate(-2).should == BigDecimal("123400.0") + BigDecimal(@arr[2]).truncate(-1).should == 0 + end + + it "returns NaN if self is NaN" do + @nan.truncate(-1).should.nan? + @nan.truncate(+1).should.nan? + @nan.truncate(0).should.nan? + end + + it "returns Infinity if self is infinite" do + @infinity.truncate(-1).should == @infinity + @infinity.truncate(+1).should == @infinity + @infinity.truncate(0).should == @infinity + + @infinity_negative.truncate(-1).should == @infinity_negative + @infinity_negative.truncate(+1).should == @infinity_negative + @infinity_negative.truncate(0).should == @infinity_negative + end + + it "returns the same value if self is special value" do + -> { @nan.truncate }.should raise_error(FloatDomainError) + -> { @infinity.truncate }.should raise_error(FloatDomainError) + -> { @infinity_negative.truncate }.should raise_error(FloatDomainError) + end +end diff --git a/spec/ruby/library/bigdecimal/uminus_spec.rb b/spec/ruby/library/bigdecimal/uminus_spec.rb new file mode 100644 index 0000000000..c780cdfac5 --- /dev/null +++ b/spec/ruby/library/bigdecimal/uminus_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#-@" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @big = BigDecimal("333E99999") + @big_neg = BigDecimal("-333E99999") + @values = [@one, @zero, @zero_pos, @zero_neg, @infinity, + @infinity_minus, @one_minus, @frac_1, @frac_2, @big, @big_neg] + end + + it "negates self" do + @one.send(:-@).should == @one_minus + @one_minus.send(:-@).should == @one + @frac_1.send(:-@).should == BigDecimal("-1E-99999") + @frac_2.send(:-@).should == BigDecimal("-0.9E-99999") + @big.send(:-@).should == @big_neg + @big_neg.send(:-@).should == @big + BigDecimal("2.221").send(:-@).should == BigDecimal("-2.221") + BigDecimal("2E10000").send(:-@).should == BigDecimal("-2E10000") + some_number = BigDecimal("2455999221.5512") + some_number_neg = BigDecimal("-2455999221.5512") + some_number.send(:-@).should == some_number_neg + (-BigDecimal("-5.5")).should == BigDecimal("5.5") + another_number = BigDecimal("-8.551551551551551551") + another_number_pos = BigDecimal("8.551551551551551551") + another_number.send(:-@).should == another_number_pos + @values.each do |val| + (val.send(:-@).send(:-@)).should == val + end + end + + it "properly handles special values" do + @infinity.send(:-@).should == @infinity_minus + @infinity_minus.send(:-@).should == @infinity + @infinity.send(:-@).infinite?.should == -1 + @infinity_minus.send(:-@).infinite?.should == 1 + + @zero.send(:-@).should == @zero + @zero.send(:-@).sign.should == -1 + @zero_pos.send(:-@).should == @zero + @zero_pos.send(:-@).sign.should == -1 + @zero_neg.send(:-@).should == @zero + @zero_neg.send(:-@).sign.should == 1 + + @nan.send(:-@).should.nan? + end +end diff --git a/spec/ruby/library/bigdecimal/uplus_spec.rb b/spec/ruby/library/bigdecimal/uplus_spec.rb new file mode 100644 index 0000000000..77483046b7 --- /dev/null +++ b/spec/ruby/library/bigdecimal/uplus_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#+@" do + it "returns the same value with same sign (twos complement)" do + first = BigDecimal("34.56") + first.send(:+@).should == first + second = BigDecimal("-34.56") + second.send(:+@).should == second + third = BigDecimal("0.0") + third.send(:+@).should == third + fourth = BigDecimal("2E1000000") + fourth.send(:+@).should == fourth + fifth = BigDecimal("123456789E-1000000") + fifth.send(:+@).should == fifth + end +end diff --git a/spec/ruby/library/bigdecimal/util_spec.rb b/spec/ruby/library/bigdecimal/util_spec.rb new file mode 100644 index 0000000000..fc67fcf200 --- /dev/null +++ b/spec/ruby/library/bigdecimal/util_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require 'bigdecimal' +require 'bigdecimal/util' + +describe "BigDecimal's util method definitions" do + describe "#to_d" do + it "should define #to_d on Integer" do + 42.to_d.should == BigDecimal(42) + end + + it "should define #to_d on Float" do + 0.5.to_d.should == BigDecimal(0.5, Float::DIG) + 1.234.to_d(2).should == BigDecimal(1.234, 2) + end + + it "should define #to_d on String" do + "0.5".to_d.should == BigDecimal(0.5, Float::DIG) + "45.67 degrees".to_d.should == BigDecimal(45.67, Float::DIG) + end + + it "should define #to_d on BigDecimal" do + bd = BigDecimal("3.14") + bd.to_d.should equal(bd) + end + + it "should define #to_d on Rational" do + Rational(22, 7).to_d(3).should == BigDecimal(3.14, 3) + end + + it "should define #to_d on nil" do + nil.to_d.should == BigDecimal(0) + end + end + + describe "#to_digits" do + it "should define #to_digits on BigDecimal" do + BigDecimal("3.14").to_digits.should == "3.14" + end + end +end diff --git a/spec/ruby/library/bigdecimal/zero_spec.rb b/spec/ruby/library/bigdecimal/zero_spec.rb new file mode 100644 index 0000000000..2563210939 --- /dev/null +++ b/spec/ruby/library/bigdecimal/zero_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#zero?" do + + it "returns true if self does equal zero" do + really_small_zero = BigDecimal("0E-200000000") + really_big_zero = BigDecimal("0E200000000000") + really_small_zero.should.zero? + really_big_zero.should.zero? + BigDecimal("0.000000000000000000000000").should.zero? + BigDecimal("0").should.zero? + BigDecimal("0E0").should.zero? + BigDecimal("+0").should.zero? + BigDecimal("-0").should.zero? + end + + it "returns false otherwise" do + BigDecimal("0000000001").should_not.zero? + BigDecimal("2E40001").should_not.zero? + BigDecimal("3E-20001").should_not.zero? + BigDecimal("Infinity").should_not.zero? + BigDecimal("-Infinity").should_not.zero? + BigDecimal("NaN").should_not.zero? + end + +end diff --git a/spec/ruby/library/cgi/cookie/domain_spec.rb b/spec/ruby/library/cgi/cookie/domain_spec.rb new file mode 100644 index 0000000000..0ed56d6d61 --- /dev/null +++ b/spec/ruby/library/cgi/cookie/domain_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#domain" do + it "returns self's domain" do + cookie = CGI::Cookie.new("test-cookie") + cookie.domain.should be_nil + + cookie = CGI::Cookie.new("name" => "test-cookie", "domain" => "example.com") + cookie.domain.should == "example.com" + end + end + + describe "CGI::Cookie#domain=" do + it "sets self's domain" do + cookie = CGI::Cookie.new("test-cookie") + cookie.domain = "test.com" + cookie.domain.should == "test.com" + + cookie.domain = "example.com" + cookie.domain.should == "example.com" + end + end +end diff --git a/spec/ruby/library/cgi/cookie/expires_spec.rb b/spec/ruby/library/cgi/cookie/expires_spec.rb new file mode 100644 index 0000000000..c5b2c4faf9 --- /dev/null +++ b/spec/ruby/library/cgi/cookie/expires_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#expires" do + it "returns self's expiration date" do + cookie = CGI::Cookie.new("test-cookie") + cookie.expires.should be_nil + + cookie = CGI::Cookie.new("name" => "test-cookie", "expires" => Time.at(1196524602)) + cookie.expires.should == Time.at(1196524602) + end + end + + describe "CGI::Cookie#expires=" do + it "sets self's expiration date" do + cookie = CGI::Cookie.new("test-cookie") + cookie.expires = Time.at(1196524602) + cookie.expires.should == Time.at(1196524602) + + cookie.expires = Time.at(1196525000) + cookie.expires.should == Time.at(1196525000) + end + end +end diff --git a/spec/ruby/library/cgi/cookie/initialize_spec.rb b/spec/ruby/library/cgi/cookie/initialize_spec.rb new file mode 100644 index 0000000000..248f35e78b --- /dev/null +++ b/spec/ruby/library/cgi/cookie/initialize_spec.rb @@ -0,0 +1,150 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#initialize when passed String" do + before :each do + @cookie = CGI::Cookie.allocate + end + + it "sets the self's name to the passed String" do + @cookie.send(:initialize, "test-cookie") + @cookie.name.should == "test-cookie" + end + + it "sets the self's value to an empty Array" do + @cookie.send(:initialize, "test-cookie") + @cookie.value.should == [] + end + + it "sets self to a non-secure cookie" do + @cookie.send(:initialize, "test") + @cookie.secure.should be_false + end + + it "does set self's path to an empty String when ENV[\"SCRIPT_NAME\"] is not set" do + @cookie.send(:initialize, "test-cookie") + @cookie.path.should == "" + end + + it "does set self's path based on ENV[\"SCRIPT_NAME\"] when ENV[\"SCRIPT_NAME\"] is set" do + old_script_name = ENV["SCRIPT_NAME"] + + begin + ENV["SCRIPT_NAME"] = "some/path/script.rb" + @cookie.send(:initialize, "test-cookie") + @cookie.path.should == "some/path/" + + ENV["SCRIPT_NAME"] = "script.rb" + @cookie.send(:initialize, "test-cookie") + @cookie.path.should == "" + + ENV["SCRIPT_NAME"] = nil + @cookie.send(:initialize, "test-cookie") + @cookie.path.should == "" + ensure + ENV["SCRIPT_NAME"] = old_script_name + end + end + + it "does not set self's expiration date" do + @cookie.expires.should be_nil + end + + it "does not set self's domain" do + @cookie.domain.should be_nil + end + end + + describe "CGI::Cookie#initialize when passed Hash" do + before :each do + @cookie = CGI::Cookie.allocate + end + + it "sets self's contents based on the passed Hash" do + @cookie.send(:initialize, + 'name' => 'test-cookie', + 'value' => ["one", "two", "three"], + 'path' => 'some/path/', + 'domain' => 'example.com', + 'expires' => Time.at(1196524602), + 'secure' => true) + + @cookie.name.should == "test-cookie" + @cookie.value.should == ["one", "two", "three"] + @cookie.path.should == "some/path/" + @cookie.domain.should == "example.com" + @cookie.expires.should == Time.at(1196524602) + @cookie.secure.should be_true + end + + it "does set self's path based on ENV[\"SCRIPT_NAME\"] when the Hash has no 'path' entry" do + old_script_name = ENV["SCRIPT_NAME"] + + begin + ENV["SCRIPT_NAME"] = "some/path/script.rb" + @cookie.send(:initialize, 'name' => 'test-cookie') + @cookie.path.should == "some/path/" + + ENV["SCRIPT_NAME"] = "script.rb" + @cookie.send(:initialize, 'name' => 'test-cookie') + @cookie.path.should == "" + + ENV["SCRIPT_NAME"] = nil + @cookie.send(:initialize, 'name' => 'test-cookie') + @cookie.path.should == "" + ensure + ENV["SCRIPT_NAME"] = old_script_name + end + end + + it "tries to convert the Hash's 'value' to an Array using #Array" do + obj = mock("Converted To Array") + obj.should_receive(:to_ary).and_return(["1", "2", "3"]) + @cookie.send(:initialize, + 'name' => 'test-cookie', + 'value' => obj) + @cookie.value.should == [ "1", "2", "3" ] + + obj = mock("Converted To Array") + obj.should_receive(:to_a).and_return(["one", "two", "three"]) + @cookie.send(:initialize, + 'name' => 'test-cookie', + 'value' => obj) + @cookie.value.should == [ "one", "two", "three" ] + + obj = mock("Put into an Array") + @cookie.send(:initialize, + 'name' => 'test-cookie', + 'value' => obj) + @cookie.value.should == [ obj ] + end + + it "raises a ArgumentError when the passed Hash has no 'name' entry" do + -> { @cookie.send(:initialize, {}) }.should raise_error(ArgumentError) + -> { @cookie.send(:initialize, "value" => "test") }.should raise_error(ArgumentError) + end + end + + describe "CGI::Cookie#initialize when passed String, values ..." do + before :each do + @cookie = CGI::Cookie.allocate + end + + it "sets the self's name to the passed String" do + @cookie.send(:initialize, "test-cookie", "one", "two", "three") + @cookie.name.should == "test-cookie" + end + + it "sets the self's value to an Array containing all passed values" do + @cookie.send(:initialize, "test-cookie", "one", "two", "three") + @cookie.value.should == ["one", "two", "three"] + end + + it "sets self to a non-secure cookie" do + @cookie.send(:initialize, "test", "one", "two", "three") + @cookie.secure.should be_false + end + end +end diff --git a/spec/ruby/library/cgi/cookie/name_spec.rb b/spec/ruby/library/cgi/cookie/name_spec.rb new file mode 100644 index 0000000000..4908204e8a --- /dev/null +++ b/spec/ruby/library/cgi/cookie/name_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#name" do + it "returns self's name" do + cookie = CGI::Cookie.new("test-cookie") + cookie.name.should == "test-cookie" + + cookie = CGI::Cookie.new("name" => "another-cookie") + cookie.name.should == "another-cookie" + end + end + + describe "CGI::Cookie#name=" do + it "sets self's expiration date" do + cookie = CGI::Cookie.new("test-cookie") + cookie.name = "another-name" + cookie.name.should == "another-name" + + cookie.name = "and-one-more" + cookie.name.should == "and-one-more" + end + end +end diff --git a/spec/ruby/library/cgi/cookie/parse_spec.rb b/spec/ruby/library/cgi/cookie/parse_spec.rb new file mode 100644 index 0000000000..bc505c37ba --- /dev/null +++ b/spec/ruby/library/cgi/cookie/parse_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie.parse" do + it "parses a raw cookie string into a hash of Cookies" do + expected = { "test-cookie" => ["one", "two", "three"] } + CGI::Cookie.parse("test-cookie=one&two&three").should == expected + + expected = { "second-cookie" => ["three", "four"], "first-cookie" => ["one", "two"] } + CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four").should == expected + end + + it "does not use , for cookie separators" do + expected = { + "first-cookie" => ["one", "two"], + "second-cookie" => ["three", "four,third_cookie=five", "six"] + } + CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four,third_cookie=five&six").should == expected + end + + it "unescapes the Cookie values" do + cookie = "test-cookie=+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E" + expected = { "test-cookie" => [ " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" ] } + CGI::Cookie.parse(cookie).should == expected + end + end +end diff --git a/spec/ruby/library/cgi/cookie/path_spec.rb b/spec/ruby/library/cgi/cookie/path_spec.rb new file mode 100644 index 0000000000..13ee5d726b --- /dev/null +++ b/spec/ruby/library/cgi/cookie/path_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#path" do + it "returns self's path" do + cookie = CGI::Cookie.new("test-cookie") + cookie.path.should == "" + + cookie = CGI::Cookie.new("name" => "test-cookie", "path" => "/some/path/") + cookie.path.should == "/some/path/" + end + end + + describe "CGI::Cookie#path=" do + it "sets self's path" do + cookie = CGI::Cookie.new("test-cookie") + cookie.path = "/some/path/" + cookie.path.should == "/some/path/" + + cookie.path = "/another/path/" + cookie.path.should == "/another/path/" + end + end +end diff --git a/spec/ruby/library/cgi/cookie/secure_spec.rb b/spec/ruby/library/cgi/cookie/secure_spec.rb new file mode 100644 index 0000000000..cb38c8c2e0 --- /dev/null +++ b/spec/ruby/library/cgi/cookie/secure_spec.rb @@ -0,0 +1,73 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#secure" do + before :each do + @cookie = CGI::Cookie.new("test-cookie") + end + + it "returns whether self is a secure cookie or not" do + @cookie.secure = true + @cookie.secure.should be_true + + @cookie.secure = false + @cookie.secure.should be_false + end + end + + describe "CGI::Cookie#secure= when passed true" do + before :each do + @cookie = CGI::Cookie.new("test-cookie") + end + + it "returns true" do + (@cookie.secure = true).should be_true + end + + it "sets self to a secure cookie" do + @cookie.secure = true + @cookie.secure.should be_true + end + end + + describe "CGI::Cookie#secure= when passed false" do + before :each do + @cookie = CGI::Cookie.new("test-cookie") + end + + it "returns false" do + (@cookie.secure = false).should be_false + end + + it "sets self to a non-secure cookie" do + @cookie.secure = false + @cookie.secure.should be_false + end + end + + describe "CGI::Cookie#secure= when passed Object" do + before :each do + @cookie = CGI::Cookie.new("test-cookie") + end + + it "does not change self's secure value" do + @cookie.secure = false + + @cookie.secure = Object.new + @cookie.secure.should be_false + + @cookie.secure = "Test" + @cookie.secure.should be_false + + @cookie.secure = true + + @cookie.secure = Object.new + @cookie.secure.should be_true + + @cookie.secure = "Test" + @cookie.secure.should be_true + end + end +end diff --git a/spec/ruby/library/cgi/cookie/to_s_spec.rb b/spec/ruby/library/cgi/cookie/to_s_spec.rb new file mode 100644 index 0000000000..20d2579f8d --- /dev/null +++ b/spec/ruby/library/cgi/cookie/to_s_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#to_s" do + it "returns a String representation of self" do + cookie = CGI::Cookie.new("test-cookie") + cookie.to_s.should == "test-cookie=; path=" + + cookie = CGI::Cookie.new("test-cookie", "value") + cookie.to_s.should == "test-cookie=value; path=" + + cookie = CGI::Cookie.new("test-cookie", "one", "two", "three") + cookie.to_s.should == "test-cookie=one&two&three; path=" + + cookie = CGI::Cookie.new( + 'name' => 'test-cookie', + 'value' => ["one", "two", "three"], + 'path' => 'some/path/', + 'domain' => 'example.com', + 'expires' => Time.at(1196524602), + 'secure' => true) + cookie.to_s.should == "test-cookie=one&two&three; domain=example.com; path=some/path/; expires=Sat, 01 Dec 2007 15:56:42 GMT; secure" + end + + it "escapes the self's values" do + cookie = CGI::Cookie.new("test-cookie", " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}") + cookie.to_s.should == "test-cookie=+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D; path=" + end + + it "does not escape tilde" do + cookie = CGI::Cookie.new("test-cookie", "~").to_s.should == "test-cookie=~; path=" + end + end +end diff --git a/spec/ruby/library/cgi/cookie/value_spec.rb b/spec/ruby/library/cgi/cookie/value_spec.rb new file mode 100644 index 0000000000..672653d7f4 --- /dev/null +++ b/spec/ruby/library/cgi/cookie/value_spec.rb @@ -0,0 +1,79 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::Cookie#value" do + it "returns self's value" do + cookie = CGI::Cookie.new("test-cookie") + cookie.value.should == [] + + cookie = CGI::Cookie.new("test-cookie", "one") + cookie.value.should == ["one"] + + cookie = CGI::Cookie.new("test-cookie", "one", "two", "three") + cookie.value.should == ["one", "two", "three"] + + cookie = CGI::Cookie.new("name" => "test-cookie", "value" => ["one", "two", "three"]) + cookie.value.should == ["one", "two", "three"] + end + + it "is in synch with self" do + fail = [] + [ + :pop, + :shift, + [:<<, "Hello"], + [:push, "Hello"], + [:unshift, "World"], + [:replace, ["A", "B"]], + [:[]=, 1, "Set"], + [:delete, "first"], + [:delete_at, 0], + ].each do |method, *args| + cookie1 = CGI::Cookie.new("test-cookie", "first", "second") + cookie2 = CGI::Cookie.new("test-cookie", "first", "second") + cookie1.send(method, *args) + cookie2.value.send(method, *args) + fail << method unless cookie1.value == cookie2.value + end + fail.should be_empty + end + end + + describe "CGI::Cookie#value=" do + before :each do + @cookie = CGI::Cookie.new("test-cookie") + end + + it "sets self's value" do + @cookie.value = ["one"] + @cookie.value.should == ["one"] + + @cookie.value = ["one", "two", "three"] + @cookie.value.should == ["one", "two", "three"] + end + + it "automatically converts the passed Object to an Array using #Array" do + @cookie.value = "test" + @cookie.value.should == ["test"] + + obj = mock("to_a") + obj.should_receive(:to_a).and_return(["1", "2"]) + @cookie.value = obj + @cookie.value.should == ["1", "2"] + + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(["1", "2"]) + @cookie.value = obj + @cookie.value.should == ["1", "2"] + end + + it "does keep self and the values in sync" do + @cookie.value = ["one", "two", "three"] + @cookie[0].should == "one" + @cookie[1].should == "two" + @cookie[2].should == "three" + end + end +end diff --git a/spec/ruby/library/cgi/escapeElement_spec.rb b/spec/ruby/library/cgi/escapeElement_spec.rb new file mode 100644 index 0000000000..72c38d6028 --- /dev/null +++ b/spec/ruby/library/cgi/escapeElement_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' +end +ruby_version_is "4.0" do + require 'cgi/escape' +end + +describe "CGI.escapeElement when passed String, elements, ..." do + it "escapes only the tags of the passed elements in the passed String" do + res = CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG") + res.should == "<BR><A HREF="url"></A>" + + res = CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) + res.should == "<BR><A HREF="url"></A>" + end + + it "is case-insensitive" do + res = CGI.escapeElement('<BR><A HREF="url"></A>', "a", "img") + res.should == '<BR><A HREF="url"></A>' + + res = CGI.escapeElement('<br><a href="url"></a>', "A", "IMG") + res.should == '<br><a href="url"></a>' + end +end diff --git a/spec/ruby/library/cgi/escapeHTML_spec.rb b/spec/ruby/library/cgi/escapeHTML_spec.rb new file mode 100644 index 0000000000..6e70e87ed7 --- /dev/null +++ b/spec/ruby/library/cgi/escapeHTML_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +begin + require 'cgi/escape' +rescue LoadError + require 'cgi' +end + +describe "CGI.escapeHTML" do + it "escapes special HTML characters (&\"<>') in the passed argument" do + CGI.escapeHTML(%[& < > " ']).should == '& < > " '' + end + + it "escapes invalid encoding" do + CGI.escapeHTML(%[<\xA4??>]).should == "<\xA4??>" + end + + it "does not escape any other characters" do + chars = " !\#$%()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + CGI.escapeHTML(chars).should == chars + end +end diff --git a/spec/ruby/library/cgi/escapeURIComponent_spec.rb b/spec/ruby/library/cgi/escapeURIComponent_spec.rb new file mode 100644 index 0000000000..1cea2b786a --- /dev/null +++ b/spec/ruby/library/cgi/escapeURIComponent_spec.rb @@ -0,0 +1,78 @@ +require_relative '../../spec_helper' +begin + require 'cgi/escape' +rescue LoadError + require 'cgi' +end + +describe "CGI.escapeURIComponent" do + it "percent-encodes characters reserved according to RFC 3986" do + # https://www.rfc-editor.org/rfc/rfc3986#section-2.2 + string = ":/?#[]@!$&'()*+,;=" + CGI.escapeURIComponent(string).should == "%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D" + end + + it "does not percent-encode unreserved characters according to RFC 3986" do + string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + CGI.escapeURIComponent(string).should == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + end + + it "encodes % character as %25" do + CGI.escapeURIComponent("%").should == "%25" + end + + # Compare to .escape which uses "+". + it "percent-encodes single whitespace" do + CGI.escapeURIComponent(" ").should == "%20" + end + + it "percent-encodes all non-reserved and non-unreserved ASCII characters" do + special_set = ":/?#[]@!$&'()*+,;=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + all_other = (0x00..0x7F).filter_map { |i| i.chr unless special_set.include?(i.chr) }.join + encoded = CGI.escapeURIComponent(all_other) + encoded.should.match?(/\A(?:%[0-9A-F]{2}){#{all_other.length}}\z/) + end + + it "percent-encodes non-ASCII bytes" do + bytes = (0x80..0xFF).map(&:chr).join + encoded = CGI.escapeURIComponent(bytes) + encoded.should.match?(/\A(?:%[0-9A-F]{2}){#{bytes.length}}\z/) + end + + it "processes multi-byte characters as separate bytes, percent-encoding each one" do + CGI.escapeURIComponent("β").should == "%CE%B2" # "β" bytes representation is CE B2 + end + + it "produces a copy of an empty string" do + string = "".encode(Encoding::BINARY) + encoded = CGI.escapeURIComponent(string) + encoded.should == "" + encoded.encoding.should == Encoding::BINARY + string.should_not.equal?(encoded) + end + + it "preserves string's encoding" do + string = "whatever".encode("ASCII-8BIT") + CGI.escapeURIComponent(string).encoding.should == Encoding::ASCII_8BIT + end + + it "processes even strings with invalid encoding, percent-encoding octets as-is" do + string = "\xC0<<".dup.force_encoding("UTF-8") + CGI.escapeURIComponent(string).should == "%C0%3C%3C" + end + + it "raises a TypeError with nil" do + -> { + CGI.escapeURIComponent(nil) + }.should raise_error(TypeError, "no implicit conversion of nil into String") + end + + it "uses implicit type conversion to String" do + object = Object.new + def object.to_str + "a b" + end + + CGI.escapeURIComponent(object).should == "a%20b" + end +end diff --git a/spec/ruby/library/cgi/escape_spec.rb b/spec/ruby/library/cgi/escape_spec.rb new file mode 100644 index 0000000000..55eb0b66c2 --- /dev/null +++ b/spec/ruby/library/cgi/escape_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +begin + require 'cgi/escape' +rescue LoadError + require 'cgi' +end + +describe "CGI.escape" do + it "url-encodes the passed argument" do + input = " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}" + expected = "+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D" + CGI.escape(input).should == expected + + input = "https://ja.wikipedia.org/wiki/\343\203\255\343\203\240\343\202\271\343\202\253\343\203\273\343\203\221\343\203\255\343\203\273\343\202\246\343\203\253\343\203\273\343\203\251\343\203\224\343\203\245\343\202\277" + expected = 'https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%E3%83%AD%E3%83%A0%E3%82%B9%E3%82%AB%E3%83%BB%E3%83%91%E3%83%AD%E3%83%BB%E3%82%A6%E3%83%AB%E3%83%BB%E3%83%A9%E3%83%94%E3%83%A5%E3%82%BF' + CGI.escape(input).should == expected + end + + it "does not escape tilde" do + CGI.escape("~").should == "~" + end +end diff --git a/spec/ruby/library/cgi/htmlextension/a_spec.rb b/spec/ruby/library/cgi/htmlextension/a_spec.rb new file mode 100644 index 0000000000..78d3dec8fa --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/a_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#a" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed a String" do + it "returns an 'a'-element, using the passed String as the 'href'-attribute" do + output = @html.a("http://www.example.com") + output.should equal_element("A", "HREF" => "http://www.example.com") + end + + it "includes the passed block's return value when passed a block" do + output = @html.a("http://www.example.com") { "Example" } + output.should equal_element("A", { "HREF" => "http://www.example.com" }, "Example") + end + end + + describe "when passed a Hash" do + it "returns an 'a'-element, using the passed Hash for attributes" do + attributes = {"HREF" => "http://www.example.com", "TARGET" => "_top"} + @html.a(attributes).should equal_element("A", attributes) + end + + it "includes the passed block's return value when passed a block" do + attributes = {"HREF" => "http://www.example.com", "TARGET" => "_top"} + @html.a(attributes) { "Example" }.should equal_element("A", attributes, "Example") + end + end + + describe "when each HTML generation" do + it "returns the doctype declaration for HTML3" do + CGISpecs.cgi_new("html3").a.should == %(<A HREF=""></A>) + CGISpecs.cgi_new("html3").a { "link text" }.should == %(<A HREF="">link text</A>) + end + + it "returns the doctype declaration for HTML4" do + CGISpecs.cgi_new("html4").a.should == %(<A HREF=""></A>) + CGISpecs.cgi_new("html4").a { "link text" }.should == %(<A HREF="">link text</A>) + end + it "returns the doctype declaration for the Transitional version of HTML4" do + CGISpecs.cgi_new("html4Tr").a.should == %(<A HREF=""></A>) + CGISpecs.cgi_new("html4Tr").a { "link text" }.should == %(<A HREF="">link text</A>) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/base_spec.rb b/spec/ruby/library/cgi/htmlextension/base_spec.rb new file mode 100644 index 0000000000..1eedfdea54 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/base_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#base" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when bassed a String" do + it "returns a 'base'-element, using the passed String as the 'href'-attribute" do + output = @html.base("http://www.example.com") + output.should equal_element("BASE", {"HREF" => "http://www.example.com"}, nil, not_closed: true) + end + + it "ignores a passed block" do + output = @html.base("http://www.example.com") { "Example" } + output.should equal_element("BASE", {"HREF" => "http://www.example.com"}, nil, not_closed: true) + end + end + + describe "when passed a Hash" do + it "returns a 'base'-element, using the passed Hash for attributes" do + output = @html.base("HREF" => "http://www.example.com", "ID" => "test") + output.should equal_element("BASE", {"HREF" => "http://www.example.com", "ID" => "test"}, nil, not_closed: true) + end + + it "ignores a passed block" do + output = @html.base("HREF" => "http://www.example.com", "ID" => "test") { "Example" } + output.should equal_element("BASE", {"HREF" => "http://www.example.com", "ID" => "test"}, nil, not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/blockquote_spec.rb b/spec/ruby/library/cgi/htmlextension/blockquote_spec.rb new file mode 100644 index 0000000000..883e36f78b --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/blockquote_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#blockquote" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed a String" do + it "returns a 'blockquote'-element, using the passed String for the 'cite'-attribute" do + output = @html.blockquote("http://www.example.com/quotes/foo.html") + output.should equal_element("BLOCKQUOTE", "CITE" => "http://www.example.com/quotes/foo.html") + end + + it "includes the passed block's return value when passed a block" do + output = @html.blockquote("http://www.example.com/quotes/foo.html") { "Foo!" } + output.should equal_element("BLOCKQUOTE", { "CITE" => "http://www.example.com/quotes/foo.html" }, "Foo!") + end + end + + describe "when passed a Hash" do + it "returns a 'blockquote'-element, using the passed Hash for attributes" do + output = @html.blockquote("CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test") + output.should equal_element("BLOCKQUOTE", "CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test") + end + + it "includes the passed block's return value when passed a block" do + output = @html.blockquote("CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test") { "Foo!" } + output.should equal_element("BLOCKQUOTE", {"CITE" => "http://www.example.com/quotes/foo.html", "ID" => "test"}, "Foo!") + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/br_spec.rb b/spec/ruby/library/cgi/htmlextension/br_spec.rb new file mode 100644 index 0000000000..23c2cb4a48 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/br_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#br" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when each HTML generation" do + it "returns the doctype declaration for HTML3" do + CGISpecs.cgi_new("html3").br.should == "<BR>" + end + + it "returns the doctype declaration for HTML4" do + CGISpecs.cgi_new("html4").br.should == "<BR>" + end + it "returns the doctype declaration for the Transitional version of HTML4" do + CGISpecs.cgi_new("html4Tr").br.should == "<BR>" + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/caption_spec.rb b/spec/ruby/library/cgi/htmlextension/caption_spec.rb new file mode 100644 index 0000000000..3d3e21ecaa --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/caption_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#caption" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed a String" do + it "returns a 'caption'-element, using the passed String for the 'align'-attribute" do + output = @html.caption("left") + output.should equal_element("CAPTION", "ALIGN" => "left") + end + + it "includes the passed block's return value when passed a block" do + output = @html.caption("left") { "Capital Cities" } + output.should equal_element("CAPTION", {"ALIGN" => "left"}, "Capital Cities") + end + end + + describe "when passed a Hash" do + it "returns a 'caption'-element, using the passed Hash for attributes" do + output = @html.caption("ALIGN" => "left", "ID" => "test") + output.should equal_element("CAPTION", "ALIGN" => "left", "ID" => "test") + end + + it "includes the passed block's return value when passed a block" do + output = @html.caption("ALIGN" => "left", "ID" => "test") { "Capital Cities" } + output.should equal_element("CAPTION", {"ALIGN" => "left", "ID" => "test"}, "Capital Cities") + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb b/spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb new file mode 100644 index 0000000000..07163c010e --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb @@ -0,0 +1,79 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#checkbox_group" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed name, values ..." do + it "returns a sequence of 'checkbox'-elements with the passed name and the passed values" do + output = CGISpecs.split(@html.checkbox_group("test", "foo", "bar", "baz")) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true) + end + + it "allows passing a value inside an Array" do + output = CGISpecs.split(@html.checkbox_group("test", ["foo"], "bar", ["baz"])) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true) + end + + it "allows passing a value as an Array containing the value and the checked state or a label" do + output = CGISpecs.split(@html.checkbox_group("test", ["foo"], ["bar", true], ["baz", "label for baz"])) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "label for baz", not_closed: true) + end + + it "allows passing a value as an Array containing the value, a label and the checked state" do + output = CGISpecs.split(@html.checkbox_group("test", ["foo", "label for foo", true], ["bar", "label for bar", false], ["baz", "label for baz", true])) + output[0].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "label for foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "label for bar", not_closed: true) + output[2].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "label for baz", not_closed: true) + end + + it "returns an empty String when passed no values" do + @html.checkbox_group("test").should == "" + end + + it "ignores a passed block" do + output = CGISpecs.split(@html.checkbox_group("test", "foo", "bar", "baz") { "test" }) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true) + end + end + + describe "when passed Hash" do + it "uses the passed Hash to generate the checkbox sequence" do + output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"])) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true) + + output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => [["foo"], ["bar", true], "baz"])) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true) + + output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "1"}, "Foo", not_closed: true) + output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "checkbox", "VALUE" => "2"}, "Bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "Baz"}, "Baz", not_closed: true) + end + + it "ignores a passed block" do + output = CGISpecs.split(@html.checkbox_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"]) { "test" }) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "checkbox", "VALUE" => "baz"}, "baz", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/checkbox_spec.rb b/spec/ruby/library/cgi/htmlextension/checkbox_spec.rb new file mode 100644 index 0000000000..ad87b78061 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/checkbox_spec.rb @@ -0,0 +1,80 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#checkbox" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns a checkbox-'input'-element without a name" do + output = @html.checkbox + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "checkbox"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.checkbox { "test" } + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "checkbox"}, "", not_closed: true) + end + end + + describe "when passed name" do + it "returns a checkbox-'input'-element with the passed name" do + output = @html.checkbox("test") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.checkbox("test") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox"}, "", not_closed: true) + end + end + + describe "CGI::HtmlExtension#checkbox when passed name, value" do + it "returns a checkbox-'input'-element with the passed name and value" do + output = @html.checkbox("test", "test-value") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.checkbox("test", "test-value") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true) + end + end + + describe "when passed name, value, checked" do + it "returns a checked checkbox-'input'-element with the passed name and value when checked is true" do + output = @html.checkbox("test", "test-value", true) + output.should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true) + + output = @html.checkbox("test", "test-value", false) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true) + + output = @html.checkbox("test", "test-value", nil) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.checkbox("test", "test-value", nil) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "checkbox", "VALUE" => "test-value"}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a checkbox-'input'-element using the passed Hash for attributes" do + attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true} + output = @html.checkbox(attributes) + output.should equal_element("INPUT", attributes, "", not_closed: true) + end + + it "ignores a passed block" do + attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true} + output = @html.checkbox(attributes) { "test" } + output.should equal_element("INPUT", attributes, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/doctype_spec.rb b/spec/ruby/library/cgi/htmlextension/doctype_spec.rb new file mode 100644 index 0000000000..02af831855 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/doctype_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#doctype" do + describe "when each HTML generation" do + it "returns the doctype declaration for HTML3" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">' + CGISpecs.cgi_new("html3").doctype.should == expect + end + + it "returns the doctype declaration for HTML4" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' + CGISpecs.cgi_new("html4").doctype.should == expect + end + + it "returns the doctype declaration for the Frameset version of HTML4" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">' + CGISpecs.cgi_new("html4Fr").doctype.should == expect + end + + it "returns the doctype declaration for the Transitional version of HTML4" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">' + CGISpecs.cgi_new("html4Tr").doctype.should == expect + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/file_field_spec.rb b/spec/ruby/library/cgi/htmlextension/file_field_spec.rb new file mode 100644 index 0000000000..eff077b9a2 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/file_field_spec.rb @@ -0,0 +1,75 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#file_field" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns a file-'input'-element without a name and a size of 20" do + output = @html.file_field + output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "", "TYPE" => "file"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.file_field { "test" } + output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "", "TYPE" => "file"}, "", not_closed: true) + end + end + + describe "when passed name" do + it "returns a checkbox-'input'-element with the passed name" do + output = @html.file_field("Example") + output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.file_field("Example") { "test" } + output.should equal_element("INPUT", {"SIZE" => 20, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true) + end + end + + describe "when passed name, size" do + it "returns a checkbox-'input'-element with the passed name and size" do + output = @html.file_field("Example", 40) + output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.file_field("Example", 40) { "test" } + output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file"}, "", not_closed: true) + end + end + + describe "when passed name, size, maxlength" do + it "returns a checkbox-'input'-element with the passed name, size and maxlength" do + output = @html.file_field("Example", 40, 100) + output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file", "MAXLENGTH" => 100}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.file_field("Example", 40, 100) { "test" } + output.should equal_element("INPUT", {"SIZE" => 40, "NAME" => "Example", "TYPE" => "file", "MAXLENGTH" => 100}, "", not_closed: true) + end + end + + describe "when passed a Hash" do + it "returns a file-'input'-element using the passed Hash for attributes" do + output = @html.file_field("NAME" => "test", "SIZE" => 40) + output.should equal_element("INPUT", {"NAME" => "test", "SIZE" => 40}, "", not_closed: true) + + output = @html.file_field("NAME" => "test", "MAXLENGTH" => 100) + output.should equal_element("INPUT", {"NAME" => "test", "MAXLENGTH" => 100}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.file_field("NAME" => "test", "SIZE" => 40) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "SIZE" => 40}, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/fixtures/common.rb b/spec/ruby/library/cgi/htmlextension/fixtures/common.rb new file mode 100644 index 0000000000..4f951f6389 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/fixtures/common.rb @@ -0,0 +1,15 @@ +module CGISpecs + def self.cgi_new(html = "html4") + old_request_method = ENV['REQUEST_METHOD'] + ENV['REQUEST_METHOD'] = "GET" + begin + CGI.new(tag_maker: html) + ensure + ENV['REQUEST_METHOD'] = old_request_method + end + end + + def self.split(string) + string.split("<").reject { |x| x.empty? }.map { |x| "<#{x}" } + end +end diff --git a/spec/ruby/library/cgi/htmlextension/form_spec.rb b/spec/ruby/library/cgi/htmlextension/form_spec.rb new file mode 100644 index 0000000000..55ac63152b --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/form_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#form" do + before :each do + @html = CGISpecs.cgi_new + @html.stub!(:script_name).and_return("/path/to/some/script") + end + + describe "when passed no arguments" do + it "returns a 'form'-element" do + output = @html.form + output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/path/to/some/script"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.form { "test" } + output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/path/to/some/script"}, "test") + end + end + + describe "when passed method" do + it "returns a 'form'-element with the passed method" do + output = @html.form("get") + output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/path/to/some/script"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.form("get") { "test" } + output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/path/to/some/script"}, "test") + end + end + + describe "when passed method, action" do + it "returns a 'form'-element with the passed method and the passed action" do + output = @html.form("get", "/some/other/script") + output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/some/other/script"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.form("get", "/some/other/script") { "test" } + output.should equal_element("FORM", {"ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ACTION" => "/some/other/script"}, "test") + end + end + + describe "when passed method, action, enctype" do + it "returns a 'form'-element with the passed method, action and enctype" do + output = @html.form("get", "/some/other/script", "multipart/form-data") + output.should equal_element("FORM", {"ENCTYPE" => "multipart/form-data", "METHOD" => "get", "ACTION" => "/some/other/script"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.form("get", "/some/other/script", "multipart/form-data") { "test" } + output.should equal_element("FORM", {"ENCTYPE" => "multipart/form-data", "METHOD" => "get", "ACTION" => "/some/other/script"}, "test") + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/frame_spec.rb b/spec/ruby/library/cgi/htmlextension/frame_spec.rb new file mode 100644 index 0000000000..fef40849eb --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/frame_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require_relative 'fixtures/common' + require 'cgi' + + describe "CGI::HtmlExtension#frame" do + before :each do + @html = CGISpecs.cgi_new("html4Fr") + end + + it "initializes the HTML Generation methods for the Frameset version of HTML4" do + @html.frameset.should == "<FRAMESET></FRAMESET>" + @html.frameset { "link text" }.should == "<FRAMESET>link text</FRAMESET>" + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/frameset_spec.rb b/spec/ruby/library/cgi/htmlextension/frameset_spec.rb new file mode 100644 index 0000000000..3ad0a9c4d2 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/frameset_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require_relative 'fixtures/common' + require 'cgi' + + describe "CGI::HtmlExtension#frameset" do + before :each do + @html = CGISpecs.cgi_new("html4Fr") + end + + it "initializes the HTML Generation methods for the Frameset version of HTML4" do + @html.frameset.should == "<FRAMESET></FRAMESET>" + @html.frameset { "link text" }.should == "<FRAMESET>link text</FRAMESET>" + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/hidden_spec.rb b/spec/ruby/library/cgi/htmlextension/hidden_spec.rb new file mode 100644 index 0000000000..b2323775f6 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/hidden_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#hidden" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an hidden-'input'-element without a name" do + output = @html.hidden + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "hidden"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.hidden { "test" } + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "hidden"}, "", not_closed: true) + end + end + + describe "when passed name" do + it "returns an hidden-'input'-element with the passed name" do + output = @html.hidden("test") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.hidden("test") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden"}, "", not_closed: true) + end + end + + describe "when passed name, value" do + it "returns an hidden-'input'-element with the passed name and value" do + output = @html.hidden("test", "some value") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden", "VALUE" => "some value"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.hidden("test", "some value") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "hidden", "VALUE" => "some value"}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a checkbox-'input'-element using the passed Hash for attributes" do + attributes = { "NAME" => "test", "VALUE" => "some value" } + output = @html.hidden("test", "some value") + output.should equal_element("INPUT", attributes, "", not_closed: true) + end + + it "ignores a passed block" do + attributes = { "NAME" => "test", "VALUE" => "some value" } + output = @html.hidden("test", "some value") { "test" } + output.should equal_element("INPUT", attributes, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/html_spec.rb b/spec/ruby/library/cgi/htmlextension/html_spec.rb new file mode 100644 index 0000000000..60a10fb6b4 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/html_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#html" do + before :each do + @html = CGISpecs.cgi_new + @html.stub!(:doctype).and_return("<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE>") + end + + describe "when passed no arguments" do + it "returns a self's doctype and an 'html'-element" do + expected = '<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE><HTML>' + @html.html.should == expected + end + + it "includes the passed block when passed a block" do + expected = '<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE><HTML>test</HTML>' + @html.html { "test" }.should == expected + end + end + + describe "when passed 'PRETTY'" do + it "returns pretty output when the passed String is 'PRETTY" do + expected = "<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE>\n<HTML>\n" + @html.html("PRETTY").should == expected + end + + it "includes the passed block when passed a block" do + expected = "<!DOCTYPE SUPA-FUNKAY-RUBYSPEC-DOCTYPE>\n<HTML>\n test\n</HTML>\n" + @html.html("PRETTY") { "test" }.should == expected + end + end + + describe "when passed a Hash" do + it "returns an 'html'-element using the passed Hash for attributes" do + expected = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML BLA="TEST">' + @html.html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">', "BLA" => "TEST").should == expected + end + + it "omits the doctype when the Hash contains a 'DOCTYPE' entry that's false or nil" do + @html.html("DOCTYPE" => false).should == "<HTML>" + @html.html("DOCTYPE" => nil).should == "<HTML>" + end + end + + describe "when each HTML generation" do + it "returns the doctype declaration for HTML3" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">' + CGISpecs.cgi_new("html3").html.should == expect + "<HTML>" + CGISpecs.cgi_new("html3").html { "html body" }.should == expect + "<HTML>html body</HTML>" + end + + it "returns the doctype declaration for HTML4" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' + CGISpecs.cgi_new("html4").html.should == expect + "<HTML>" + CGISpecs.cgi_new("html4").html { "html body" }.should == expect + "<HTML>html body</HTML>" + end + + it "returns the doctype declaration for the Transitional version of HTML4" do + expect = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">' + CGISpecs.cgi_new("html4Tr").html.should == expect + "<HTML>" + CGISpecs.cgi_new("html4Tr").html { "html body" }.should == expect + "<HTML>html body</HTML>" + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/image_button_spec.rb b/spec/ruby/library/cgi/htmlextension/image_button_spec.rb new file mode 100644 index 0000000000..f8770119d4 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/image_button_spec.rb @@ -0,0 +1,72 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#image_button" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an image-'input'-element without a source image" do + output = @html.image_button + output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.image_button { "test" } + output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image"}, "", not_closed: true) + end + end + + describe "when passed src" do + it "returns an image-'input'-element with the passed src" do + output = @html.image_button("/path/to/image.png") + output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.image_button("/path/to/image.png") { "test" } + output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image"}, "", not_closed: true) + end + end + + describe "when passed src, name" do + it "returns an image-'input'-element with the passed src and name" do + output = @html.image_button("/path/to/image.png", "test") + output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.image_button("/path/to/image.png", "test") { "test" } + output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test"}, "", not_closed: true) + end + end + + describe "when passed src, name, alt" do + it "returns an image-'input'-element with the passed src, name and alt" do + output = @html.image_button("/path/to/image.png", "test", "alternative") + output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test", "ALT" => "alternative"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.image_button("/path/to/image.png", "test", "alternative") { "test" } + output.should equal_element("INPUT", {"SRC" => "/path/to/image.png", "TYPE" => "image", "NAME" => "test", "ALT" => "alternative"}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a image-'input'-element using the passed Hash for attributes" do + output = @html.image_button("NAME" => "test", "VALUE" => "test-value") + output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image", "NAME" => "test", "VALUE" => "test-value"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.image_button("NAME" => "test", "VALUE" => "test-value") { "test" } + output.should equal_element("INPUT", {"SRC" => "", "TYPE" => "image", "NAME" => "test", "VALUE" => "test-value"}, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/img_spec.rb b/spec/ruby/library/cgi/htmlextension/img_spec.rb new file mode 100644 index 0000000000..a05cfdea48 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/img_spec.rb @@ -0,0 +1,86 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#img" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an 'img'-element without an src-url or alt-text" do + output = @html.img + output.should equal_element("IMG", { "SRC" => "", "ALT" => "" }, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.img { "test" } + output.should equal_element("IMG", { "SRC" => "", "ALT" => "" }, "", not_closed: true) + end + end + + describe "when passed src" do + it "returns an 'img'-element with the passed src-url" do + output = @html.img("/path/to/some/image.png") + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "" }, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.img("/path/to/some/image.png") + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "" }, "", not_closed: true) + end + end + + describe "when passed src, alt" do + it "returns an 'img'-element with the passed src-url and the passed alt-text" do + output = @html.img("/path/to/some/image.png", "Alternative") + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative" }, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.img("/path/to/some/image.png", "Alternative") { "test" } + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative" }, "", not_closed: true) + end + end + + describe "when passed src, alt, width" do + it "returns an 'img'-element with the passed src-url, the passed alt-text and the passed width" do + output = @html.img("/path/to/some/image.png", "Alternative", 40) + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative", "WIDTH" => "40" }, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.img("/path/to/some/image.png", "Alternative", 40) { "test" } + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative", "WIDTH" => "40" }, "", not_closed: true) + end + end + + describe "when passed src, alt, width, height" do + it "returns an 'img'-element with the passed src-url, the passed alt-text, the passed width and the passed height" do + output = @html.img("/path/to/some/image.png", "Alternative", 40, 60) + output.should equal_element("IMG", { "SRC" => "/path/to/some/image.png", "ALT" => "Alternative", "WIDTH" => "40", "HEIGHT" => "60" }, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.img { "test" } + output.should equal_element("IMG", { "SRC" => "", "ALT" => "" }, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns an 'img'-element with the passed Hash as attributes" do + attributes = { "SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50 } + output = @html.img(attributes) + output.should equal_element("IMG", attributes, "", not_closed: true) + end + + it "ignores a passed block" do + attributes = { "SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50 } + output = @html.img(attributes) { "test" } + output.should equal_element("IMG", attributes, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb b/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb new file mode 100644 index 0000000000..ee1c45b84e --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb @@ -0,0 +1,67 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#multipart_form" do + before :each do + @html = CGISpecs.cgi_new + @html.stub!(:script_name).and_return("/path/to/some/script.rb") + end + + describe "when passed no arguments" do + it "returns a 'form'-element with it's enctype set to multipart" do + output = @html.multipart_form + output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post" }, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.multipart_form { "test" } + output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post" }, "test") + end + end + + describe "when passed action" do + it "returns a 'form'-element with the passed action" do + output = @html.multipart_form("/some/other/script.rb") + output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.multipart_form("/some/other/script.rb") { "test" } + output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "test") + end + end + + describe "when passed action, enctype" do + it "returns a 'form'-element with the passed action and enctype" do + output = @html.multipart_form("/some/other/script.rb", "application/x-www-form-urlencoded") + output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.multipart_form("/some/other/script.rb", "application/x-www-form-urlencoded") { "test" } + output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "post", "ACTION" => "/some/other/script.rb" }, "test") + end + end + + describe "when passed Hash" do + it "returns a 'form'-element with the passed Hash as attributes" do + output = @html.multipart_form("ID" => "test") + output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ID" => "test" }, "") + + output = @html.multipart_form("ID" => "test", "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get") + output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ID" => "test" }, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.multipart_form("ID" => "test") { "test" } + output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post", "ID" => "test" }, "test") + + output = @html.multipart_form("ID" => "test", "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get") { "test" } + output.should equal_element("FORM", { "ENCTYPE" => "application/x-www-form-urlencoded", "METHOD" => "get", "ID" => "test" }, "test") + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/password_field_spec.rb b/spec/ruby/library/cgi/htmlextension/password_field_spec.rb new file mode 100644 index 0000000000..0fefdd5c45 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/password_field_spec.rb @@ -0,0 +1,87 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#password_field" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an password-'input'-element without a name" do + output = @html.password_field + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.password_field { "test" } + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true) + end + end + + describe "when passed name" do + it "returns an password-'input'-element with the passed name" do + output = @html.password_field("test") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.password_field("test") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "SIZE" => "40"}, "", not_closed: true) + end + end + + describe "when passed name, value" do + it "returns an password-'input'-element with the passed name and value" do + output = @html.password_field("test", "some value") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.password_field("test", "some value") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true) + end + end + + describe "when passed name, value, size" do + it "returns an password-'input'-element with the passed name, value and size" do + output = @html.password_field("test", "some value", 60) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.password_field("test", "some value", 60) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true) + end + end + + describe "when passed name, value, size, maxlength" do + it "returns an password-'input'-element with the passed name, value, size and maxlength" do + output = @html.password_field("test", "some value", 60, 12) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.password_field("test", "some value", 60, 12) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "password", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a checkbox-'input'-element using the passed Hash for attributes" do + output = @html.password_field("NAME" => "test", "VALUE" => "some value") + output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "password" }, "", not_closed: true) + + output = @html.password_field("TYPE" => "hidden") + output.should equal_element("INPUT", {"TYPE" => "password"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.password_field("NAME" => "test", "VALUE" => "some value") { "test" } + output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "password" }, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb b/spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb new file mode 100644 index 0000000000..7452d15317 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + require_relative 'shared/popup_menu' + + describe "CGI::HtmlExtension#popup_menu" do + it_behaves_like :cgi_htmlextension_popup_menu, :popup_menu + end +end diff --git a/spec/ruby/library/cgi/htmlextension/radio_button_spec.rb b/spec/ruby/library/cgi/htmlextension/radio_button_spec.rb new file mode 100644 index 0000000000..8458685cdc --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/radio_button_spec.rb @@ -0,0 +1,80 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#radio_button" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns a radio-'input'-element without a name" do + output = @html.radio_button + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "radio"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.radio_button { "test" } + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "radio"}, "", not_closed: true) + end + end + + describe "when passed name" do + it "returns a radio-'input'-element with the passed name" do + output = @html.radio_button("test") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.radio_button("test") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio"}, "", not_closed: true) + end + end + + describe "CGI::HtmlExtension#checkbox when passed name, value" do + it "returns a radio-'input'-element with the passed name and value" do + output = @html.radio_button("test", "test-value") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.radio_button("test", "test-value") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true) + end + end + + describe "when passed name, value, checked" do + it "returns a checked radio-'input'-element with the passed name and value when checked is true" do + output = @html.radio_button("test", "test-value", true) + output.should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true) + + output = @html.radio_button("test", "test-value", false) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true) + + output = @html.radio_button("test", "test-value", nil) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.radio_button("test", "test-value", nil) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "test-value"}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a radio-'input'-element using the passed Hash for attributes" do + attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true} + output = @html.radio_button(attributes) + output.should equal_element("INPUT", attributes, "", not_closed: true) + end + + it "ignores a passed block" do + attributes = {"NAME" => "test", "VALUE" => "test-value", "CHECKED" => true} + output = @html.radio_button(attributes) { "test" } + output.should equal_element("INPUT", attributes, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/radio_group_spec.rb b/spec/ruby/library/cgi/htmlextension/radio_group_spec.rb new file mode 100644 index 0000000000..fd925a5165 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/radio_group_spec.rb @@ -0,0 +1,80 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#radio_group" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed name, values ..." do + it "returns a sequence of 'radio'-elements with the passed name and the passed values" do + output = CGISpecs.split(@html.radio_group("test", "foo", "bar", "baz")) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true) + end + + it "allows passing a value inside an Array" do + output = CGISpecs.split(@html.radio_group("test", ["foo"], "bar", ["baz"])) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true) + end + + it "allows passing a value as an Array containing the value and the checked state or a label" do + output = CGISpecs.split(@html.radio_group("test", ["foo"], ["bar", true], ["baz", "label for baz"])) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "label for baz", not_closed: true) + end + + # TODO: CGI does not like passing false instead of true. + it "allows passing a value as an Array containing the value, a label and the checked state" do + output = CGISpecs.split(@html.radio_group("test", ["foo", "label for foo", true], ["bar", "label for bar", false], ["baz", "label for baz", true])) + output[0].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "label for foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "label for bar", not_closed: true) + output[2].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "label for baz", not_closed: true) + end + + it "returns an empty String when passed no values" do + @html.radio_group("test").should == "" + end + + it "ignores a passed block" do + output = CGISpecs.split(@html.radio_group("test", "foo", "bar", "baz") { "test" }) + output[0].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "test", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true) + end + end + + describe "when passed Hash" do + it "uses the passed Hash to generate the radio sequence" do + output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"])) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true) + + output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => [["foo"], ["bar", true], "baz"])) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true) + + output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "1"}, "Foo", not_closed: true) + output[1].should equal_element("INPUT", {"CHECKED" => true, "NAME" => "name", "TYPE" => "radio", "VALUE" => "2"}, "Bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "Baz"}, "Baz", not_closed: true) + end + + it "ignores a passed block" do + output = CGISpecs.split(@html.radio_group("NAME" => "name", "VALUES" => ["foo", "bar", "baz"]) { "test" }) + output[0].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "foo"}, "foo", not_closed: true) + output[1].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "bar"}, "bar", not_closed: true) + output[2].should equal_element("INPUT", {"NAME" => "name", "TYPE" => "radio", "VALUE" => "baz"}, "baz", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/reset_spec.rb b/spec/ruby/library/cgi/htmlextension/reset_spec.rb new file mode 100644 index 0000000000..80e4441b16 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/reset_spec.rb @@ -0,0 +1,60 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#reset" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns a reset-'input'-element" do + output = @html.reset + output.should equal_element("INPUT", {"TYPE" => "reset"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.reset { "test" } + output.should equal_element("INPUT", {"TYPE" => "reset"}, "", not_closed: true) + end + end + + describe "when passed value" do + it "returns a reset-'input'-element with the passed value" do + output = @html.reset("Example") + output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.reset("Example") { "test" } + output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true) + end + end + + describe "when passed value, name" do + it "returns a reset-'input'-element with the passed value and the passed name" do + output = @html.reset("Example", "test-name") + output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.reset("Example", "test-name") { "test" } + output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a reset-'input'-element with the passed value" do + output = @html.reset("Example") + output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.reset("Example") { "test" } + output.should equal_element("INPUT", {"TYPE" => "reset", "VALUE" => "Example"}, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb b/spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb new file mode 100644 index 0000000000..b565444679 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require_relative 'fixtures/common' + require 'cgi' + require_relative 'shared/popup_menu' + + describe "CGI::HtmlExtension#scrolling_list" do + it_behaves_like :cgi_htmlextension_popup_menu, :scrolling_list + end +end diff --git a/spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb b/spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb new file mode 100644 index 0000000000..4787fd3f8e --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb @@ -0,0 +1,94 @@ +describe :cgi_htmlextension_popup_menu, shared: true do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an empty 'select'-element without a name" do + output = @html.send(@method) + output.should equal_element("SELECT", {"NAME" => ""}, "") + end + + it "ignores a passed block" do + output = @html.send(@method) { "test" } + output.should equal_element("SELECT", {"NAME" => ""}, "") + end + end + + describe "when passed name, values ..." do + it "returns a 'select'-element with the passed name containing 'option'-elements based on the passed values" do + content = @html.option("VALUE" => "foo") { "foo" } + content << @html.option("VALUE" => "bar") { "bar" } + content << @html.option("VALUE" => "baz") { "baz" } + + output = @html.send(@method, "test", "foo", "bar", "baz") + output.should equal_element("SELECT", {"NAME" => "test"}, content) + end + + it "allows passing values inside of arrays" do + content = @html.option("VALUE" => "foo") { "foo" } + content << @html.option("VALUE" => "bar") { "bar" } + content << @html.option("VALUE" => "baz") { "baz" } + + output = @html.send(@method, "test", ["foo"], ["bar"], ["baz"]) + output.should equal_element("SELECT", {"NAME" => "test"}, content) + end + + it "allows passing a value as an Array containing the value and the select state or a label" do + content = @html.option("VALUE" => "foo") { "foo" } + content << @html.option("VALUE" => "bar", "SELECTED" => true) { "bar" } + content << @html.option("VALUE" => "baz") { "baz" } + + output = @html.send(@method, "test", ["foo"], ["bar", true], "baz") + output.should equal_element("SELECT", {"NAME" => "test"}, content) + end + + it "allows passing a value as an Array containing the value, a label and the select state" do + content = @html.option("VALUE" => "1") { "Foo" } + content << @html.option("VALUE" => "2", "SELECTED" => true) { "Bar" } + content << @html.option("VALUE" => "Baz") { "Baz" } + + output = @html.send(@method, "test", ["1", "Foo"], ["2", "Bar", true], "Baz") + output.should equal_element("SELECT", {"NAME" => "test"}, content) + end + + it "ignores a passed block" do + content = @html.option("VALUE" => "foo") { "foo" } + content << @html.option("VALUE" => "bar") { "bar" } + content << @html.option("VALUE" => "baz") { "baz" } + + output = @html.send(@method, "test", "foo", "bar", "baz") { "woot" } + output.should equal_element("SELECT", {"NAME" => "test"}, content) + end + end + + describe "when passed a Hash" do + it "uses the passed Hash to generate the 'select'-element and the 'option'-elements" do + attributes = { + "NAME" => "test", "SIZE" => 2, "MULTIPLE" => true, + "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"] + } + + content = @html.option("VALUE" => "1") { "Foo" } + content << @html.option("VALUE" => "2", "SELECTED" => true) { "Bar" } + content << @html.option("VALUE" => "Baz") { "Baz" } + + output = @html.send(@method, attributes) + output.should equal_element("SELECT", {"NAME" => "test", "SIZE" => 2, "MULTIPLE" => true}, content) + end + + it "ignores a passed block" do + attributes = { + "NAME" => "test", "SIZE" => 2, "MULTIPLE" => true, + "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"] + } + + content = @html.option("VALUE" => "1") { "Foo" } + content << @html.option("VALUE" => "2", "SELECTED" => true) { "Bar" } + content << @html.option("VALUE" => "Baz") { "Baz" } + + output = @html.send(@method, attributes) { "testing" } + output.should equal_element("SELECT", {"NAME" => "test", "SIZE" => 2, "MULTIPLE" => true}, content) + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/submit_spec.rb b/spec/ruby/library/cgi/htmlextension/submit_spec.rb new file mode 100644 index 0000000000..bb6e079c4e --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/submit_spec.rb @@ -0,0 +1,60 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#submit" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns a submit-'input'-element" do + output = @html.submit + output.should equal_element("INPUT", {"TYPE" => "submit"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.submit { "test" } + output.should equal_element("INPUT", {"TYPE" => "submit"}, "", not_closed: true) + end + end + + describe "when passed value" do + it "returns a submit-'input'-element with the passed value" do + output = @html.submit("Example") + output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.submit("Example") { "test" } + output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true) + end + end + + describe "when passed value, name" do + it "returns a submit-'input'-element with the passed value and the passed name" do + output = @html.submit("Example", "test-name") + output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.submit("Example", "test-name") { "test" } + output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example", "NAME" => "test-name"}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a submit-'input'-element with the passed value" do + output = @html.submit("Example") + output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.submit("Example") { "test" } + output.should equal_element("INPUT", {"TYPE" => "submit", "VALUE" => "Example"}, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/text_field_spec.rb b/spec/ruby/library/cgi/htmlextension/text_field_spec.rb new file mode 100644 index 0000000000..37e13e3746 --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/text_field_spec.rb @@ -0,0 +1,87 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#text_field" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an text-'input'-element without a name" do + output = @html.text_field + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.text_field { "test" } + output.should equal_element("INPUT", {"NAME" => "", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true) + end + end + + describe "when passed name" do + it "returns an text-'input'-element with the passed name" do + output = @html.text_field("test") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.text_field("test") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "SIZE" => "40"}, "", not_closed: true) + end + end + + describe "when passed name, value" do + it "returns an text-'input'-element with the passed name and value" do + output = @html.text_field("test", "some value") + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.text_field("test", "some value") { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "40"}, "", not_closed: true) + end + end + + describe "when passed name, value, size" do + it "returns an text-'input'-element with the passed name, value and size" do + output = @html.text_field("test", "some value", 60) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.text_field("test", "some value", 60) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60"}, "", not_closed: true) + end + end + + describe "when passed name, value, size, maxlength" do + it "returns an text-'input'-element with the passed name, value, size and maxlength" do + output = @html.text_field("test", "some value", 60, 12) + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.text_field("test", "some value", 60, 12) { "test" } + output.should equal_element("INPUT", {"NAME" => "test", "TYPE" => "text", "VALUE" => "some value", "SIZE" => "60", "MAXLENGTH" => 12}, "", not_closed: true) + end + end + + describe "when passed Hash" do + it "returns a checkbox-'input'-element using the passed Hash for attributes" do + output = @html.text_field("NAME" => "test", "VALUE" => "some value") + output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "text" }, "", not_closed: true) + + output = @html.text_field("TYPE" => "hidden") + output.should equal_element("INPUT", {"TYPE" => "text"}, "", not_closed: true) + end + + it "ignores a passed block" do + output = @html.text_field("NAME" => "test", "VALUE" => "some value") { "test" } + output.should equal_element("INPUT", { "NAME" => "test", "VALUE" => "some value", "TYPE" => "text" }, "", not_closed: true) + end + end + end +end diff --git a/spec/ruby/library/cgi/htmlextension/textarea_spec.rb b/spec/ruby/library/cgi/htmlextension/textarea_spec.rb new file mode 100644 index 0000000000..99c6d3dd2d --- /dev/null +++ b/spec/ruby/library/cgi/htmlextension/textarea_spec.rb @@ -0,0 +1,76 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'fixtures/common' + + describe "CGI::HtmlExtension#textarea" do + before :each do + @html = CGISpecs.cgi_new + end + + describe "when passed no arguments" do + it "returns an 'textarea'-element without a name" do + output = @html.textarea + output.should equal_element("TEXTAREA", {"NAME" => "", "COLS" => "70", "ROWS" => "10"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.textarea { "Example" } + output.should equal_element("TEXTAREA", {"NAME" => "", "COLS" => "70", "ROWS" => "10"}, "Example") + end + end + + describe "when passed name" do + it "returns an 'textarea'-element with the passed name" do + output = @html.textarea("test") + output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "70", "ROWS" => "10"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.textarea("test") { "Example" } + output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "70", "ROWS" => "10"}, "Example") + end + end + + describe "when passed name, cols" do + it "returns an 'textarea'-element with the passed name and the passed amount of columns" do + output = @html.textarea("test", 40) + output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "10"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.textarea("test", 40) { "Example" } + output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "10"}, "Example") + end + end + + describe "when passed name, cols, rows" do + it "returns an 'textarea'-element with the passed name, the passed amount of columns and the passed number of rows" do + output = @html.textarea("test", 40, 5) + output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "5"}, "") + end + + it "includes the return value of the passed block when passed a block" do + output = @html.textarea("test", 40, 5) { "Example" } + output.should equal_element("TEXTAREA", {"NAME" => "test", "COLS" => "40", "ROWS" => "5"}, "Example") + end + end + + describe "when passed Hash" do + it "uses the passed Hash as attributes" do + @html.textarea("ID" => "test").should == '<TEXTAREA ID="test"></TEXTAREA>' + + attributes = {"ID" => "test-id", "NAME" => "test-name"} + output = @html.textarea(attributes) + output.should equal_element("TEXTAREA", attributes, "") + end + + it "includes the return value of the passed block when passed a block" do + attributes = {"ID" => "test-id", "NAME" => "test-name"} + output = @html.textarea(attributes) { "test" } + output.should equal_element("TEXTAREA", attributes, "test") + end + end + end +end diff --git a/spec/ruby/library/cgi/http_header_spec.rb b/spec/ruby/library/cgi/http_header_spec.rb new file mode 100644 index 0000000000..8d9f3fe9b8 --- /dev/null +++ b/spec/ruby/library/cgi/http_header_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + require_relative 'shared/http_header' + + describe "CGI#http_header" do + it_behaves_like :cgi_http_header, :http_header + end +end diff --git a/spec/ruby/library/cgi/initialize_spec.rb b/spec/ruby/library/cgi/initialize_spec.rb new file mode 100644 index 0000000000..b8ecf5cac2 --- /dev/null +++ b/spec/ruby/library/cgi/initialize_spec.rb @@ -0,0 +1,136 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI#initialize" do + it "is private" do + CGI.should have_private_instance_method(:initialize) + end + end + + describe "CGI#initialize when passed no arguments" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.allocate + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "extends self with CGI::QueryExtension" do + @cgi.send(:initialize) + @cgi.should be_kind_of(CGI::QueryExtension) + end + + it "does not extend self with CGI::HtmlExtension" do + @cgi.send(:initialize) + @cgi.should_not be_kind_of(CGI::HtmlExtension) + end + + it "does not extend self with any of the other HTML modules" do + @cgi.send(:initialize) + @cgi.should_not be_kind_of(CGI::HtmlExtension) + @cgi.should_not be_kind_of(CGI::Html3) + @cgi.should_not be_kind_of(CGI::Html4) + @cgi.should_not be_kind_of(CGI::Html4Tr) + @cgi.should_not be_kind_of(CGI::Html4Fr) + end + + it "sets #cookies based on ENV['HTTP_COOKIE']" do + begin + old_env, ENV["HTTP_COOKIE"] = ENV["HTTP_COOKIE"], "test=test yay" + @cgi.send(:initialize) + @cgi.cookies.should == { "test"=>[ "test yay" ] } + ensure + ENV["HTTP_COOKIE"] = old_env + end + end + + it "sets #params based on ENV['QUERY_STRING'] when ENV['REQUEST_METHOD'] is GET" do + begin + old_env_query, ENV["QUERY_STRING"] = ENV["QUERY_STRING"], "?test=a&test2=b" + old_env_method, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], "GET" + @cgi.send(:initialize) + @cgi.params.should == { "test2" => ["b"], "?test" => ["a"] } + ensure + ENV["QUERY_STRING"] = old_env_query + ENV["REQUEST_METHOD"] = old_env_method + end + end + + it "sets #params based on ENV['QUERY_STRING'] when ENV['REQUEST_METHOD'] is HEAD" do + begin + old_env_query, ENV["QUERY_STRING"] = ENV["QUERY_STRING"], "?test=a&test2=b" + old_env_method, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], "HEAD" + @cgi.send(:initialize) + @cgi.params.should == { "test2" => ["b"], "?test" => ["a"] } + ensure + ENV["QUERY_STRING"] = old_env_query + ENV["REQUEST_METHOD"] = old_env_method + end + end + end + + describe "CGI#initialize when passed type" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.allocate + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + + it "extends self with CGI::QueryExtension" do + @cgi.send(:initialize, "test") + @cgi.should be_kind_of(CGI::QueryExtension) + end + + it "extends self with CGI::QueryExtension, CGI::Html3 and CGI::HtmlExtension when the passed type is 'html3'" do + @cgi.send(:initialize, "html3") + @cgi.should be_kind_of(CGI::Html3) + @cgi.should be_kind_of(CGI::HtmlExtension) + @cgi.should be_kind_of(CGI::QueryExtension) + + @cgi.should_not be_kind_of(CGI::Html4) + @cgi.should_not be_kind_of(CGI::Html4Tr) + @cgi.should_not be_kind_of(CGI::Html4Fr) + end + + it "extends self with CGI::QueryExtension, CGI::Html4 and CGI::HtmlExtension when the passed type is 'html4'" do + @cgi.send(:initialize, "html4") + @cgi.should be_kind_of(CGI::Html4) + @cgi.should be_kind_of(CGI::HtmlExtension) + @cgi.should be_kind_of(CGI::QueryExtension) + + @cgi.should_not be_kind_of(CGI::Html3) + @cgi.should_not be_kind_of(CGI::Html4Tr) + @cgi.should_not be_kind_of(CGI::Html4Fr) + end + + it "extends self with CGI::QueryExtension, CGI::Html4Tr and CGI::HtmlExtension when the passed type is 'html4Tr'" do + @cgi.send(:initialize, "html4Tr") + @cgi.should be_kind_of(CGI::Html4Tr) + @cgi.should be_kind_of(CGI::HtmlExtension) + @cgi.should be_kind_of(CGI::QueryExtension) + + @cgi.should_not be_kind_of(CGI::Html3) + @cgi.should_not be_kind_of(CGI::Html4) + @cgi.should_not be_kind_of(CGI::Html4Fr) + end + + it "extends self with CGI::QueryExtension, CGI::Html4Tr, CGI::Html4Fr and CGI::HtmlExtension when the passed type is 'html4Fr'" do + @cgi.send(:initialize, "html4Fr") + @cgi.should be_kind_of(CGI::Html4Tr) + @cgi.should be_kind_of(CGI::Html4Fr) + @cgi.should be_kind_of(CGI::HtmlExtension) + @cgi.should be_kind_of(CGI::QueryExtension) + + @cgi.should_not be_kind_of(CGI::Html3) + @cgi.should_not be_kind_of(CGI::Html4) + end + end +end diff --git a/spec/ruby/library/cgi/out_spec.rb b/spec/ruby/library/cgi/out_spec.rb new file mode 100644 index 0000000000..733e656ea1 --- /dev/null +++ b/spec/ruby/library/cgi/out_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI#out" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + $stdout, @old_stdout = IOStub.new, $stdout + end + + after :each do + $stdout = @old_stdout + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "it writes a HTMl header based on the passed argument to $stdout" do + @cgi.out { "" } + $stdout.should == "Content-Type: text/html\r\nContent-Length: 0\r\n\r\n" + end + + it "appends the block's return value to the HTML header" do + @cgi.out { "test!" } + $stdout.should == "Content-Type: text/html\r\nContent-Length: 5\r\n\r\ntest!" + end + + it "automatically sets the Content-Length Header based on the block's return value" do + @cgi.out { "0123456789" } + $stdout.should == "Content-Type: text/html\r\nContent-Length: 10\r\n\r\n0123456789" + end + + it "includes Cookies in the @output_cookies field" do + @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"]) + @cgi.out { "" } + $stdout.should == "Content-Type: text/html\r\nContent-Length: 0\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n" + end + end + + describe "CGI#out when passed no block" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "raises a LocalJumpError" do + -> { @cgi.out }.should raise_error(LocalJumpError) + end + end +end diff --git a/spec/ruby/library/cgi/parse_spec.rb b/spec/ruby/library/cgi/parse_spec.rb new file mode 100644 index 0000000000..f09270c195 --- /dev/null +++ b/spec/ruby/library/cgi/parse_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI.parse when passed String" do + it "parses a HTTP Query String into a Hash" do + CGI.parse("test=test&a=b").should == { "a" => ["b"], "test" => ["test"] } + CGI.parse("test=1,2,3").should == { "test" => ["1,2,3"] } + CGI.parse("test=a&a=&b=").should == { "test" => ["a"], "a" => [""], "b" => [""] } + end + + it "parses query strings with semicolons in place of ampersands" do + CGI.parse("test=test;a=b").should == { "a" => ["b"], "test" => ["test"] } + CGI.parse("test=a;a=;b=").should == { "test" => ["a"], "a" => [""], "b" => [""] } + end + + it "allows passing multiple values for one key" do + CGI.parse("test=1&test=2&test=3").should == { "test" => ["1", "2", "3"] } + CGI.parse("test[]=1&test[]=2&test[]=3").should == { "test[]" => [ "1", "2", "3" ] } + end + + it "unescapes keys and values" do + CGI.parse("hello%3F=hello%21").should == { "hello?" => ["hello!"] } + end + end +end diff --git a/spec/ruby/library/cgi/pretty_spec.rb b/spec/ruby/library/cgi/pretty_spec.rb new file mode 100644 index 0000000000..9df1611037 --- /dev/null +++ b/spec/ruby/library/cgi/pretty_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI.pretty when passed html" do + it "indents the passed html String with two spaces" do + CGI.pretty("<HTML><BODY></BODY></HTML>").should == <<-EOS +<HTML> + <BODY> + </BODY> +</HTML> +EOS + end + end + + describe "CGI.pretty when passed html, indentation_unit" do + it "indents the passed html String with the passed indentation_unit" do + CGI.pretty("<HTML><BODY></BODY></HTML>", "\t").should == <<-EOS +<HTML> +\t<BODY> +\t</BODY> +</HTML> +EOS + end + end +end diff --git a/spec/ruby/library/cgi/print_spec.rb b/spec/ruby/library/cgi/print_spec.rb new file mode 100644 index 0000000000..f4f461c5c0 --- /dev/null +++ b/spec/ruby/library/cgi/print_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI#print" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "passes all arguments to $stdout.print" do + $stdout.should_receive(:print).with("test") + @cgi.print("test") + + $stdout.should_receive(:print).with("one", "two", "three", ["four", "five"]) + @cgi.print("one", "two", "three", ["four", "five"]) + end + + it "returns the result of calling $stdout.print" do + $stdout.should_receive(:print).with("test").and_return(:expected) + @cgi.print("test").should == :expected + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/accept_charset_spec.rb b/spec/ruby/library/cgi/queryextension/accept_charset_spec.rb new file mode 100644 index 0000000000..be05f0c175 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/accept_charset_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#accept_charset" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_ACCEPT_CHARSET']" do + old_value, ENV['HTTP_ACCEPT_CHARSET'] = ENV['HTTP_ACCEPT_CHARSET'], "ISO-8859-1,utf-8;q=0.7,*;q=0.7" + begin + @cgi.accept_charset.should == "ISO-8859-1,utf-8;q=0.7,*;q=0.7" + ensure + ENV['HTTP_ACCEPT_CHARSET'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb b/spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb new file mode 100644 index 0000000000..42eb4a49b5 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#accept_encoding" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_ACCEPT_ENCODING']" do + old_value, ENV['HTTP_ACCEPT_ENCODING'] = ENV['HTTP_ACCEPT_ENCODING'], "gzip,deflate" + begin + @cgi.accept_encoding.should == "gzip,deflate" + ensure + ENV['HTTP_ACCEPT_ENCODING'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/accept_language_spec.rb b/spec/ruby/library/cgi/queryextension/accept_language_spec.rb new file mode 100644 index 0000000000..19f29c6345 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/accept_language_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#accept_language" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_ACCEPT_LANGUAGE']" do + old_value, ENV['HTTP_ACCEPT_LANGUAGE'] = ENV['HTTP_ACCEPT_LANGUAGE'], "en-us,en;q=0.5" + begin + @cgi.accept_language.should == "en-us,en;q=0.5" + ensure + ENV['HTTP_ACCEPT_LANGUAGE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/accept_spec.rb b/spec/ruby/library/cgi/queryextension/accept_spec.rb new file mode 100644 index 0000000000..dcae39a736 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/accept_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#accept" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_ACCEPT']" do + old_value, ENV['HTTP_ACCEPT'] = ENV['HTTP_ACCEPT'], "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" + begin + @cgi.accept.should == "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" + ensure + ENV['HTTP_ACCEPT'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/auth_type_spec.rb b/spec/ruby/library/cgi/queryextension/auth_type_spec.rb new file mode 100644 index 0000000000..75e9cdb27a --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/auth_type_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#auth_type" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['AUTH_TYPE']" do + old_value, ENV['AUTH_TYPE'] = ENV['AUTH_TYPE'], "Basic" + begin + @cgi.auth_type.should == "Basic" + ensure + ENV['AUTH_TYPE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/cache_control_spec.rb b/spec/ruby/library/cgi/queryextension/cache_control_spec.rb new file mode 100644 index 0000000000..c4b727e671 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/cache_control_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#cache_control" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_CACHE_CONTROL']" do + old_value, ENV['HTTP_CACHE_CONTROL'] = ENV['HTTP_CACHE_CONTROL'], "no-cache" + begin + @cgi.cache_control.should == "no-cache" + ensure + ENV['HTTP_CACHE_CONTROL'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/content_length_spec.rb b/spec/ruby/library/cgi/queryextension/content_length_spec.rb new file mode 100644 index 0000000000..a8b87e148c --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/content_length_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#content_length" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['CONTENT_LENGTH'] as Integer" do + old_value = ENV['CONTENT_LENGTH'] + begin + ENV['CONTENT_LENGTH'] = nil + @cgi.content_length.should be_nil + + ENV['CONTENT_LENGTH'] = "100" + @cgi.content_length.should eql(100) + ensure + ENV['CONTENT_LENGTH'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/content_type_spec.rb b/spec/ruby/library/cgi/queryextension/content_type_spec.rb new file mode 100644 index 0000000000..d3cbdf0b14 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/content_type_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#content_type" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['CONTENT_TYPE']" do + old_value, ENV['CONTENT_TYPE'] = ENV['CONTENT_TYPE'], "text/html" + begin + @cgi.content_type.should == "text/html" + ensure + ENV['CONTENT_TYPE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/cookies_spec.rb b/spec/ruby/library/cgi/queryextension/cookies_spec.rb new file mode 100644 index 0000000000..266fe0c721 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/cookies_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#cookies" do + it "needs to be reviewed for spec completeness" + end + + describe "CGI::QueryExtension#cookies=" do + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/library/cgi/queryextension/element_reference_spec.rb b/spec/ruby/library/cgi/queryextension/element_reference_spec.rb new file mode 100644 index 0000000000..0b57387efc --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/element_reference_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#[]" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + ENV['QUERY_STRING'], @old_query_string = "one=a&two=b&two=c", ENV['QUERY_STRING'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + ENV['QUERY_STRING'] = @old_query_string + end + + it "it returns the value for the parameter with the given key" do + @cgi["one"].should == "a" + end + + it "only returns the first value for parameters with multiple values" do + @cgi["two"].should == "b" + end + + it "returns a String" do + @cgi["one"].should be_kind_of(String) + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/from_spec.rb b/spec/ruby/library/cgi/queryextension/from_spec.rb new file mode 100644 index 0000000000..b341e0be10 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/from_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#from" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_FROM']" do + old_value, ENV['HTTP_FROM'] = ENV['HTTP_FROM'], "googlebot(at)googlebot.com" + begin + @cgi.from.should == "googlebot(at)googlebot.com" + ensure + ENV['HTTP_FROM'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb b/spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb new file mode 100644 index 0000000000..c82522326b --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#gateway_interface" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['GATEWAY_INTERFACE']" do + old_value, ENV['GATEWAY_INTERFACE'] = ENV['GATEWAY_INTERFACE'], "CGI/1.1" + begin + @cgi.gateway_interface.should == "CGI/1.1" + ensure + ENV['GATEWAY_INTERFACE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/has_key_spec.rb b/spec/ruby/library/cgi/queryextension/has_key_spec.rb new file mode 100644 index 0000000000..43f7aae1b2 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/has_key_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'shared/has_key' + + describe "CGI::QueryExtension#has_key?" do + it_behaves_like :cgi_query_extension_has_key_p, :has_key? + end +end diff --git a/spec/ruby/library/cgi/queryextension/host_spec.rb b/spec/ruby/library/cgi/queryextension/host_spec.rb new file mode 100644 index 0000000000..e1047c942b --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/host_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#host" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_HOST']" do + old_value, ENV['HTTP_HOST'] = ENV['HTTP_HOST'], "localhost" + begin + @cgi.host.should == "localhost" + ensure + ENV['HTTP_HOST'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/include_spec.rb b/spec/ruby/library/cgi/queryextension/include_spec.rb new file mode 100644 index 0000000000..7275c309f9 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/include_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'shared/has_key' + + describe "CGI::QueryExtension#include?" do + it_behaves_like :cgi_query_extension_has_key_p, :include? + end +end diff --git a/spec/ruby/library/cgi/queryextension/key_spec.rb b/spec/ruby/library/cgi/queryextension/key_spec.rb new file mode 100644 index 0000000000..dc2f52fbe0 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/key_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require_relative 'shared/has_key' + + describe "CGI::QueryExtension#key?" do + it_behaves_like :cgi_query_extension_has_key_p, :key? + end +end diff --git a/spec/ruby/library/cgi/queryextension/keys_spec.rb b/spec/ruby/library/cgi/queryextension/keys_spec.rb new file mode 100644 index 0000000000..bb16914065 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/keys_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#keys" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + ENV['QUERY_STRING'], @old_query_string = "one=a&two=b", ENV['QUERY_STRING'] + + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + ENV['QUERY_STRING'] = @old_query_string + end + + it "returns all parameter keys as an Array" do + @cgi.keys.sort.should == ["one", "two"] + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/multipart_spec.rb b/spec/ruby/library/cgi/queryextension/multipart_spec.rb new file mode 100644 index 0000000000..281791892c --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/multipart_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + require "stringio" + + describe "CGI::QueryExtension#multipart?" do + before :each do + @old_stdin = $stdin + + @old_request_method = ENV['REQUEST_METHOD'] + @old_content_type = ENV['CONTENT_TYPE'] + @old_content_length = ENV['CONTENT_LENGTH'] + + ENV['REQUEST_METHOD'] = "POST" + ENV["CONTENT_TYPE"] = "multipart/form-data; boundary=---------------------------1137522503144128232716531729" + ENV["CONTENT_LENGTH"] = "222" + + $stdin = StringIO.new <<-EOS +-----------------------------1137522503144128232716531729\r +Content-Disposition: form-data; name="file"; filename=""\r +Content-Type: application/octet-stream\r +\r +\r +-----------------------------1137522503144128232716531729--\r +EOS + + @cgi = CGI.new + end + + after :each do + $stdin = @old_stdin + + ENV['REQUEST_METHOD'] = @old_request_method + ENV['CONTENT_TYPE'] = @old_content_type + ENV['CONTENT_LENGTH'] = @old_content_length + end + + it "returns true if the current Request is a multipart request" do + @cgi.multipart?.should be_true + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/negotiate_spec.rb b/spec/ruby/library/cgi/queryextension/negotiate_spec.rb new file mode 100644 index 0000000000..4083e6a8cd --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/negotiate_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#negotiate" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_NEGOTIATE']" do + old_value, ENV['HTTP_NEGOTIATE'] = ENV['HTTP_NEGOTIATE'], "trans" + begin + @cgi.negotiate.should == "trans" + ensure + ENV['HTTP_NEGOTIATE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/params_spec.rb b/spec/ruby/library/cgi/queryextension/params_spec.rb new file mode 100644 index 0000000000..938028ea14 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/params_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#params" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + ENV['QUERY_STRING'], @old_query_string = "one=a&two=b&two=c&three", ENV['QUERY_STRING'] + @cgi = CGI.new + end + + after :each do + ENV['QUERY_STRING'] = @old_query_string + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns the parsed HTTP Query Params" do + @cgi.params.should == {"three"=>[], "two"=>["b", "c"], "one"=>["a"]} + end + end + + describe "CGI::QueryExtension#params=" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "sets the HTTP Query Params to the passed argument" do + @cgi.params.should == {} + + @cgi.params = {"one"=>["a"], "two"=>["b", "c"]} + @cgi.params.should == {"one"=>["a"], "two"=>["b", "c"]} + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/path_info_spec.rb b/spec/ruby/library/cgi/queryextension/path_info_spec.rb new file mode 100644 index 0000000000..9b7834c514 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/path_info_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#path_info" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['PATH_INFO']" do + old_value, ENV['PATH_INFO'] = ENV['PATH_INFO'], "/test/path" + begin + @cgi.path_info.should == "/test/path" + ensure + ENV['PATH_INFO'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/path_translated_spec.rb b/spec/ruby/library/cgi/queryextension/path_translated_spec.rb new file mode 100644 index 0000000000..a773aaafdb --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/path_translated_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#path_translated" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['PATH_TRANSLATED']" do + old_value, ENV['PATH_TRANSLATED'] = ENV['PATH_TRANSLATED'], "/full/path/to/dir" + begin + @cgi.path_translated.should == "/full/path/to/dir" + ensure + ENV['PATH_TRANSLATED'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/pragma_spec.rb b/spec/ruby/library/cgi/queryextension/pragma_spec.rb new file mode 100644 index 0000000000..be384182a5 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/pragma_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#pragma" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_PRAGMA']" do + old_value, ENV['HTTP_PRAGMA'] = ENV['HTTP_PRAGMA'], "no-cache" + begin + @cgi.pragma.should == "no-cache" + ensure + ENV['HTTP_PRAGMA'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/query_string_spec.rb b/spec/ruby/library/cgi/queryextension/query_string_spec.rb new file mode 100644 index 0000000000..64fbeaea10 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/query_string_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#query_string" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['QUERY_STRING']" do + old_value, ENV['QUERY_STRING'] = ENV['QUERY_STRING'], "one=a&two=b" + begin + @cgi.query_string.should == "one=a&two=b" + ensure + ENV['QUERY_STRING'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb b/spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb new file mode 100644 index 0000000000..30d314aca1 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#raw_cookie2" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_COOKIE2']" do + old_value, ENV['HTTP_COOKIE2'] = ENV['HTTP_COOKIE2'], "some_cookie=data" + begin + @cgi.raw_cookie2.should == "some_cookie=data" + ensure + ENV['HTTP_COOKIE2'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb b/spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb new file mode 100644 index 0000000000..affa504b39 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#raw_cookie" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_COOKIE']" do + old_value, ENV['HTTP_COOKIE'] = ENV['HTTP_COOKIE'], "some_cookie=data" + begin + @cgi.raw_cookie.should == "some_cookie=data" + ensure + ENV['HTTP_COOKIE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/referer_spec.rb b/spec/ruby/library/cgi/queryextension/referer_spec.rb new file mode 100644 index 0000000000..53fc19ddd0 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/referer_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#referer" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_REFERER']" do + old_value, ENV['HTTP_REFERER'] = ENV['HTTP_REFERER'], "example.com" + begin + @cgi.referer.should == "example.com" + ensure + ENV['HTTP_REFERER'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/remote_addr_spec.rb b/spec/ruby/library/cgi/queryextension/remote_addr_spec.rb new file mode 100644 index 0000000000..7b5addc2d5 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/remote_addr_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#remote_addr" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['REMOTE_ADDR']" do + old_value, ENV['REMOTE_ADDR'] = ENV['REMOTE_ADDR'], "127.0.0.1" + begin + @cgi.remote_addr.should == "127.0.0.1" + ensure + ENV['REMOTE_ADDR'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/remote_host_spec.rb b/spec/ruby/library/cgi/queryextension/remote_host_spec.rb new file mode 100644 index 0000000000..2dfe59ca38 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/remote_host_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#remote_host" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['REMOTE_HOST']" do + old_value, ENV['REMOTE_HOST'] = ENV['REMOTE_HOST'], "test.host" + begin + @cgi.remote_host.should == "test.host" + ensure + ENV['REMOTE_HOST'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/remote_ident_spec.rb b/spec/ruby/library/cgi/queryextension/remote_ident_spec.rb new file mode 100644 index 0000000000..bb05fc7942 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/remote_ident_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#remote_ident" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['REMOTE_IDENT']" do + old_value, ENV['REMOTE_IDENT'] = ENV['REMOTE_IDENT'], "no-idea-what-this-is-for" + begin + @cgi.remote_ident.should == "no-idea-what-this-is-for" + ensure + ENV['REMOTE_IDENT'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/remote_user_spec.rb b/spec/ruby/library/cgi/queryextension/remote_user_spec.rb new file mode 100644 index 0000000000..29856302ab --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/remote_user_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#remote_user" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['REMOTE_USER']" do + old_value, ENV['REMOTE_USER'] = ENV['REMOTE_USER'], "username" + begin + @cgi.remote_user.should == "username" + ensure + ENV['REMOTE_USER'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/request_method_spec.rb b/spec/ruby/library/cgi/queryextension/request_method_spec.rb new file mode 100644 index 0000000000..7331b134d2 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/request_method_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#request_method" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['REQUEST_METHOD']" do + old_value, ENV['REQUEST_METHOD'] = ENV['REQUEST_METHOD'], "GET" + begin + @cgi.request_method.should == "GET" + ensure + ENV['REQUEST_METHOD'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/script_name_spec.rb b/spec/ruby/library/cgi/queryextension/script_name_spec.rb new file mode 100644 index 0000000000..4b359a545f --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/script_name_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#script_name" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['SCRIPT_NAME']" do + old_value, ENV['SCRIPT_NAME'] = ENV['SCRIPT_NAME'], "/path/to/script.rb" + begin + @cgi.script_name.should == "/path/to/script.rb" + ensure + ENV['SCRIPT_NAME'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/server_name_spec.rb b/spec/ruby/library/cgi/queryextension/server_name_spec.rb new file mode 100644 index 0000000000..c1f7fb4c54 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/server_name_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#server_name" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['SERVER_NAME']" do + old_value, ENV['SERVER_NAME'] = ENV['SERVER_NAME'], "localhost" + begin + @cgi.server_name.should == "localhost" + ensure + ENV['SERVER_NAME'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/server_port_spec.rb b/spec/ruby/library/cgi/queryextension/server_port_spec.rb new file mode 100644 index 0000000000..60c03ea639 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/server_port_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#server_port" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['SERVER_PORT'] as Integer" do + old_value = ENV['SERVER_PORT'] + begin + ENV['SERVER_PORT'] = nil + @cgi.server_port.should be_nil + + ENV['SERVER_PORT'] = "3000" + @cgi.server_port.should eql(3000) + ensure + ENV['SERVER_PORT'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/server_protocol_spec.rb b/spec/ruby/library/cgi/queryextension/server_protocol_spec.rb new file mode 100644 index 0000000000..fdbcc2108f --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/server_protocol_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#server_protocol" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['SERVER_PROTOCOL']" do + old_value, ENV['SERVER_PROTOCOL'] = ENV['SERVER_PROTOCOL'], "HTTP/1.1" + begin + @cgi.server_protocol.should == "HTTP/1.1" + ensure + ENV['SERVER_PROTOCOL'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/server_software_spec.rb b/spec/ruby/library/cgi/queryextension/server_software_spec.rb new file mode 100644 index 0000000000..c5811a2268 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/server_software_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#server_software" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['SERVER_SOFTWARE']" do + old_value, ENV['SERVER_SOFTWARE'] = ENV['SERVER_SOFTWARE'], "Server/1.0.0" + begin + @cgi.server_software.should == "Server/1.0.0" + ensure + ENV['SERVER_SOFTWARE'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/queryextension/shared/has_key.rb b/spec/ruby/library/cgi/queryextension/shared/has_key.rb new file mode 100644 index 0000000000..cfac5865fa --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/shared/has_key.rb @@ -0,0 +1,19 @@ +describe :cgi_query_extension_has_key_p, shared: true do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + ENV['QUERY_STRING'], @old_query_string = "one=a&two=b", ENV['QUERY_STRING'] + + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + ENV['QUERY_STRING'] = @old_query_string + end + + it "returns true when the passed key exists in the HTTP Query" do + @cgi.send(@method, "one").should be_true + @cgi.send(@method, "two").should be_true + @cgi.send(@method, "three").should be_false + end +end diff --git a/spec/ruby/library/cgi/queryextension/user_agent_spec.rb b/spec/ruby/library/cgi/queryextension/user_agent_spec.rb new file mode 100644 index 0000000000..3240352ef6 --- /dev/null +++ b/spec/ruby/library/cgi/queryextension/user_agent_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI::QueryExtension#user_agent" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + it "returns ENV['HTTP_USER_AGENT']" do + old_value, ENV['HTTP_USER_AGENT'] = ENV['HTTP_USER_AGENT'], "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_2; de-de) AppleWebKit/527+ (KHTML, like Gecko) Version/3.1 Safari/525.13" + begin + @cgi.user_agent.should == "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_2; de-de) AppleWebKit/527+ (KHTML, like Gecko) Version/3.1 Safari/525.13" + ensure + ENV['HTTP_USER_AGENT'] = old_value + end + end + end +end diff --git a/spec/ruby/library/cgi/rfc1123_date_spec.rb b/spec/ruby/library/cgi/rfc1123_date_spec.rb new file mode 100644 index 0000000000..636185f22c --- /dev/null +++ b/spec/ruby/library/cgi/rfc1123_date_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' + + describe "CGI.rfc1123_date when passed Time" do + it "returns the passed Time formatted in RFC1123 ('Sat, 01 Dec 2007 15:56:42 GMT')" do + input = Time.at(1196524602) + expected = 'Sat, 01 Dec 2007 15:56:42 GMT' + CGI.rfc1123_date(input).should == expected + end + end +end diff --git a/spec/ruby/library/cgi/shared/http_header.rb b/spec/ruby/library/cgi/shared/http_header.rb new file mode 100644 index 0000000000..b225b5925e --- /dev/null +++ b/spec/ruby/library/cgi/shared/http_header.rb @@ -0,0 +1,112 @@ +require_relative '../../../spec_helper' +require 'cgi' + +describe :cgi_http_header, shared: true do + describe "CGI#http_header when passed no arguments" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + + it "returns a HTTP header specifying the Content-Type as text/html" do + @cgi.send(@method).should == "Content-Type: text/html\r\n\r\n" + end + + it "includes Cookies in the @output_cookies field" do + @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"]) + @cgi.send(@method).should == "Content-Type: text/html\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n" + end + end + + describe "CGI#http_header when passed String" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + + it "returns a HTTP header specifying the Content-Type as the passed String's content" do + @cgi.send(@method, "text/plain").should == "Content-Type: text/plain\r\n\r\n" + end + + it "includes Cookies in the @output_cookies field" do + @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"]) + @cgi.send(@method, "text/plain").should == "Content-Type: text/plain\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n" + end + end + + describe "CGI#http_header when passed Hash" do + before :each do + ENV['REQUEST_METHOD'], @old_request_method = "GET", ENV['REQUEST_METHOD'] + @cgi = CGI.new + end + + after :each do + ENV['REQUEST_METHOD'] = @old_request_method + end + + + it "returns a HTTP header based on the Hash's key/value pairs" do + header = @cgi.send(@method, "type" => "text/plain") + header.should == "Content-Type: text/plain\r\n\r\n" + + header = @cgi.send(@method, "type" => "text/plain", "charset" => "UTF-8") + header.should == "Content-Type: text/plain; charset=UTF-8\r\n\r\n" + + header = @cgi.send(@method, "nph" => true) + header.should include("HTTP/1.0 200 OK\r\n") + header.should include("Date: ") + header.should include("Server: ") + header.should include("Connection: close\r\n") + header.should include("Content-Type: text/html\r\n") + + header = @cgi.send(@method, "status" => "OK") + header.should == "Status: 200 OK\r\nContent-Type: text/html\r\n\r\n" + + header = @cgi.send(@method, "status" => "PARTIAL_CONTENT") + header.should == "Status: 206 Partial Content\r\nContent-Type: text/html\r\n\r\n" + + header = @cgi.send(@method, "status" => "MULTIPLE_CHOICES") + header.should == "Status: 300 Multiple Choices\r\nContent-Type: text/html\r\n\r\n" + + header = @cgi.send(@method, "server" => "Server Software used") + header.should == "Server: Server Software used\r\nContent-Type: text/html\r\n\r\n" + + header = @cgi.send(@method, "connection" => "connection type") + header.should == "Connection: connection type\r\nContent-Type: text/html\r\n\r\n" + + header = @cgi.send(@method, "length" => 103) + header.should == "Content-Type: text/html\r\nContent-Length: 103\r\n\r\n" + + header = @cgi.send(@method, "language" => "ja") + header.should == "Content-Type: text/html\r\nContent-Language: ja\r\n\r\n" + + header = @cgi.send(@method, "expires" => Time.at(0)) + header.should == "Content-Type: text/html\r\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n" + + header = @cgi.send(@method, "cookie" => "my cookie's content") + header.should == "Content-Type: text/html\r\nSet-Cookie: my cookie's content\r\n\r\n" + + header = @cgi.send(@method, "cookie" => ["multiple", "cookies"]) + header.should == "Content-Type: text/html\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n" + end + + it "includes Cookies in the @output_cookies field" do + @cgi.instance_variable_set(:@output_cookies, ["multiple", "cookies"]) + @cgi.send(@method, {}).should == "Content-Type: text/html\r\nSet-Cookie: multiple\r\nSet-Cookie: cookies\r\n\r\n" + end + + it "returns a HTTP header specifying the Content-Type as text/html when passed an empty Hash" do + @cgi.send(@method, {}).should == "Content-Type: text/html\r\n\r\n" + end + end +end diff --git a/spec/ruby/library/cgi/unescapeElement_spec.rb b/spec/ruby/library/cgi/unescapeElement_spec.rb new file mode 100644 index 0000000000..db83f0d2fb --- /dev/null +++ b/spec/ruby/library/cgi/unescapeElement_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' +end +ruby_version_is "4.0" do + require 'cgi/escape' +end + +describe "CGI.unescapeElement when passed String, elements, ..." do + it "unescapes only the tags of the passed elements in the passed String" do + res = CGI.unescapeElement("<BR><A HREF="url"></A>", "A", "IMG") + res.should == '<BR><A HREF="url"></A>' + + res = CGI.unescapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) + res.should == '<BR><A HREF="url"></A>' + end + + it "is case-insensitive" do + res = CGI.unescapeElement("<BR><A HREF="url"></A>", "a", "img") + res.should == '<BR><A HREF="url"></A>' + + res = CGI.unescapeElement("<br><a href="url"></a>", "A", "IMG") + res.should == '<br><a href="url"></a>' + end +end diff --git a/spec/ruby/library/cgi/unescapeHTML_spec.rb b/spec/ruby/library/cgi/unescapeHTML_spec.rb new file mode 100644 index 0000000000..e43dcc83e5 --- /dev/null +++ b/spec/ruby/library/cgi/unescapeHTML_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +begin + require 'cgi/escape' +rescue LoadError + require 'cgi' +end + +describe "CGI.unescapeHTML" do + it "unescapes '& < > "' to '& < > \"'" do + input = '& < > "' + expected = '& < > "' + CGI.unescapeHTML(input).should == expected + end + + it "doesn't unescape other html entities such as '©' or '&heart'" do + input = '©&heart;' + expected = input + CGI.unescapeHTML(input).should == expected + end + + it "unescapes 'c' format entities" do + input = '"&'<>' + expected = '"&\'<>' + CGI.unescapeHTML(input).should == expected + end + + it "unescapes '香' format entities" do + input = '"&'<>' + expected = '"&\'<>' + CGI.unescapeHTML(input).should == expected + end + + it "leaves invalid formatted strings" do + input = '&<&>"&abcdefghijklmn' + expected = '&<&>"&abcdefghijklmn' + CGI.unescapeHTML(input).should == expected + end + + it "leaves partial invalid &# at end of string" do + input = "fooooooo&#" + CGI.unescapeHTML(input).should == input + end + + it "unescapes invalid encoding" do + input = "\xFF&" + CGI.unescapeHTML(input).should == input + end +end diff --git a/spec/ruby/library/cgi/unescapeURIComponent_spec.rb b/spec/ruby/library/cgi/unescapeURIComponent_spec.rb new file mode 100644 index 0000000000..f80eb1626b --- /dev/null +++ b/spec/ruby/library/cgi/unescapeURIComponent_spec.rb @@ -0,0 +1,128 @@ +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' +end +ruby_version_is "4.0" do + require 'cgi/escape' +end + +describe "CGI.unescapeURIComponent" do + it "decodes any percent-encoded octets to their corresponding bytes according to RFC 3986" do + string = (0x00..0xff).map { |i| "%%%02x" % i }.join + expected = (0x00..0xff).map { |i| i.chr }.join.force_encoding(Encoding::UTF_8) + CGI.unescapeURIComponent(string).should == expected + end + + it "disregards case of characters in a percent-encoding triplet" do + CGI.unescapeURIComponent("%CE%B2abc").should == "βabc" + CGI.unescapeURIComponent("%ce%b2ABC").should == "βABC" + end + + it "leaves any non-percent-encoded characters as-is" do + string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ:/?#[]@!$&'()*+,;=\t\x0D\xFFβᛉ▒90%" + decoded = CGI.unescapeURIComponent(string) + decoded.should == string + string.should_not.equal?(decoded) + end + + it "leaves sequences which can't be a percent-encoded octet as-is" do + string = "%AZ%B" + decoded = CGI.unescapeURIComponent(string) + decoded.should == string + string.should_not.equal?(decoded) + end + + it "creates a String with the specified target Encoding" do + string = CGI.unescapeURIComponent("%D2%3C%3CABC", Encoding::ISO_8859_1) + string.encoding.should == Encoding::ISO_8859_1 + string.should == "Ò<<ABC".encode("ISO-8859-1") + end + + it "accepts a string name of an Encoding" do + CGI.unescapeURIComponent("%D2%3C%3CABC", "ISO-8859-1").should == "Ò<<ABC".encode("ISO-8859-1") + end + + it "raises ArgumentError if specified encoding is unknown" do + -> { CGI.unescapeURIComponent("ABC", "ISO-JOKE-1") }.should raise_error(ArgumentError, "unknown encoding name - ISO-JOKE-1") + end + + ruby_version_is ""..."4.0" do + it "uses CGI.accept_charset as the default target encoding" do + original_charset = CGI.accept_charset + CGI.accept_charset = "ISO-8859-1" + decoded = CGI.unescapeURIComponent("%D2%3C%3CABC") + decoded.should == "Ò<<ABC".encode("ISO-8859-1") + decoded.encoding.should == Encoding::ISO_8859_1 + ensure + CGI.accept_charset = original_charset + end + + it "has CGI.accept_charset as UTF-8 by default" do + decoded = CGI.unescapeURIComponent("%CE%B2ABC") + decoded.should == "βABC" + decoded.encoding.should == Encoding::UTF_8 + end + end + + ruby_version_is "4.0" do + # "cgi/escape" does not have methods to access @@accept_charset. + # Full "cgi" gem provides them, allowing to possibly change it. + it "uses CGI's @@accept_charset as the default target encoding" do + original_charset = CGI.class_variable_get(:@@accept_charset) + CGI.class_variable_set(:@@accept_charset, "ISO-8859-1") + decoded = CGI.unescapeURIComponent("%D2%3C%3CABC") + decoded.should == "Ò<<ABC".encode("ISO-8859-1") + decoded.encoding.should == Encoding::ISO_8859_1 + ensure + CGI.class_variable_set(:@@accept_charset, original_charset) + end + + it "has CGI's @@accept_charset as UTF-8 by default" do + decoded = CGI.unescapeURIComponent("%CE%B2ABC") + decoded.should == "βABC" + decoded.encoding.should == Encoding::UTF_8 + end + end + + context "when source string specifies octets invalid in target encoding" do + it "uses source string's encoding" do + string = "%A2%A6%A3".encode(Encoding::SHIFT_JIS) + decoded = CGI.unescapeURIComponent(string, Encoding::US_ASCII) + decoded.encoding.should == Encoding::SHIFT_JIS + decoded.should == "「ヲ」".encode(Encoding::SHIFT_JIS) + decoded.valid_encoding?.should be_true + end + + it "uses source string's encoding even if it's also invalid" do + string = "%FF".encode(Encoding::US_ASCII) + decoded = CGI.unescapeURIComponent(string, Encoding::SHIFT_JIS) + decoded.encoding.should == Encoding::US_ASCII + decoded.should == "\xFF".dup.force_encoding(Encoding::US_ASCII) + decoded.valid_encoding?.should be_false + end + end + + it "decodes an empty string as an empty string with target encoding" do + string = "".encode(Encoding::BINARY) + decoded = CGI.unescapeURIComponent(string, "UTF-8") + decoded.should == "" + decoded.encoding.should == Encoding::UTF_8 + string.should_not.equal?(decoded) + end + + it "raises a TypeError with nil" do + -> { + CGI.unescapeURIComponent(nil) + }.should raise_error(TypeError, "no implicit conversion of nil into String") + end + + it "uses implicit type conversion to String" do + object = Object.new + def object.to_str + "a%20b" + end + + CGI.unescapeURIComponent(object).should == "a b" + end +end diff --git a/spec/ruby/library/cgi/unescape_spec.rb b/spec/ruby/library/cgi/unescape_spec.rb new file mode 100644 index 0000000000..aa731b9367 --- /dev/null +++ b/spec/ruby/library/cgi/unescape_spec.rb @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' + +ruby_version_is ""..."4.0" do + require 'cgi' +end +ruby_version_is "4.0" do + require 'cgi/escape' +end + +describe "CGI.unescape" do + it "url-decodes the passed argument" do + input = "+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E" + expected = " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + CGI.unescape(input).should == expected + + input = 'https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%E3%83%AD%E3%83%A0%E3%82%B9%E3%82%AB%E3%83%BB%E3%83%91%E3%83%AD%E3%83%BB%E3%82%A6%E3%83%AB%E3%83%BB%E3%83%A9%E3%83%94%E3%83%A5%E3%82%BF' + expected = "https://ja.wikipedia.org/wiki/\343\203\255\343\203\240\343\202\271\343\202\253\343\203\273\343\203\221\343\203\255\343\203\273\343\202\246\343\203\253\343\203\273\343\203\251\343\203\224\343\203\245\343\202\277" + CGI.unescape(input).should == expected + end +end diff --git a/spec/ruby/library/coverage/fixtures/code_with_begin.rb b/spec/ruby/library/coverage/fixtures/code_with_begin.rb new file mode 100644 index 0000000000..9a6384e337 --- /dev/null +++ b/spec/ruby/library/coverage/fixtures/code_with_begin.rb @@ -0,0 +1,3 @@ +begin + 'coverage with begin' +end diff --git a/spec/ruby/library/coverage/fixtures/eval_code.rb b/spec/ruby/library/coverage/fixtures/eval_code.rb new file mode 100644 index 0000000000..8ab82218f3 --- /dev/null +++ b/spec/ruby/library/coverage/fixtures/eval_code.rb @@ -0,0 +1,11 @@ +5 + 5 + +module CoverageSpecs + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + attr_reader :ok + RUBY + +end + +4 + 4 diff --git a/spec/ruby/library/coverage/fixtures/second_class.rb b/spec/ruby/library/coverage/fixtures/second_class.rb new file mode 100644 index 0000000000..0318ac26ff --- /dev/null +++ b/spec/ruby/library/coverage/fixtures/second_class.rb @@ -0,0 +1,5 @@ +class SecondClass + def some_method + 42 + end +end diff --git a/spec/ruby/library/coverage/fixtures/some_class.rb b/spec/ruby/library/coverage/fixtures/some_class.rb new file mode 100644 index 0000000000..52629f0332 --- /dev/null +++ b/spec/ruby/library/coverage/fixtures/some_class.rb @@ -0,0 +1,16 @@ + +#Class comment +class SomeClass + + # Method comment + def some_method + + # Inline method comment + actual_code = true + + end + +end + +# Trailing comment and extra blank line + diff --git a/spec/ruby/library/coverage/fixtures/start_coverage.rb b/spec/ruby/library/coverage/fixtures/start_coverage.rb new file mode 100644 index 0000000000..8a0c56c50a --- /dev/null +++ b/spec/ruby/library/coverage/fixtures/start_coverage.rb @@ -0,0 +1,3 @@ +2 + 2 +Coverage.start +1 + 1 diff --git a/spec/ruby/library/coverage/peek_result_spec.rb b/spec/ruby/library/coverage/peek_result_spec.rb new file mode 100644 index 0000000000..9d7c890faa --- /dev/null +++ b/spec/ruby/library/coverage/peek_result_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' +require 'coverage' + +describe 'Coverage.peek_result' do + before :all do + @class_file = fixture __FILE__, 'some_class.rb' + @second_class_file = fixture __FILE__, 'second_class.rb' + end + + after :each do + $LOADED_FEATURES.delete(@class_file) + $LOADED_FEATURES.delete(@second_class_file) + end + + it 'returns the result so far' do + Coverage.start + require @class_file.chomp('.rb') + result = Coverage.peek_result + Coverage.result + + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + end + + it 'immediate second call returns same result' do + Coverage.start + require @class_file.chomp('.rb') + result1 = Coverage.peek_result + result2 = Coverage.peek_result + Coverage.result + + result2.should == result1 + end + + it 'second call after require returns accumulated result' do + Coverage.start + require @class_file.chomp('.rb') + Coverage.peek_result + require @second_class_file.chomp('.rb') + result = Coverage.peek_result + Coverage.result + + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ], + @second_class_file => [ + 1, 1, 0, nil, nil + ] + } + end + + it 'call right before Coverage.result should give equal result' do + Coverage.start + require @class_file.chomp('.rb') + result1 = Coverage.peek_result + result2 = Coverage.result + + result1.should == result2 + end +end diff --git a/spec/ruby/library/coverage/result_spec.rb b/spec/ruby/library/coverage/result_spec.rb new file mode 100644 index 0000000000..0101eb6a64 --- /dev/null +++ b/spec/ruby/library/coverage/result_spec.rb @@ -0,0 +1,343 @@ +require_relative '../../spec_helper' +require 'coverage' + +describe 'Coverage.result' do + before :all do + @class_file = fixture __FILE__, 'some_class.rb' + @config_file = fixture __FILE__, 'start_coverage.rb' + @eval_code_file = fixture __FILE__, 'eval_code.rb' + @with_begin_file = fixture __FILE__, 'code_with_begin.rb' + end + + before :each do + Coverage.running?.should == false + end + + after :each do + $LOADED_FEATURES.delete(@class_file) + $LOADED_FEATURES.delete(@config_file) + $LOADED_FEATURES.delete(@eval_code_file) + $LOADED_FEATURES.delete(@with_begin_file) + + Coverage.result if Coverage.running? + end + + it 'gives the covered files as a hash with arrays of count or nil' do + Coverage.start + require @class_file.chomp('.rb') + result = Coverage.result + + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + end + + it 'returns results for each mode separately when enabled :all modes' do + Coverage.start(:all) + require @class_file.chomp('.rb') + result = Coverage.result + + result.should == { + @class_file => { + lines: [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ], + branches: {}, + methods: { + [SomeClass, :some_method, 6, 2, 11, 5] => 0 + } + } + } + end + + it 'returns results for each mode separately when enabled any mode explicitly' do + Coverage.start(lines: true) + require @class_file.chomp('.rb') + result = Coverage.result + + result.should == { + @class_file => + { + lines: [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + } + end + + it 'no requires/loads should give empty hash' do + Coverage.start + result = Coverage.result + + result.should == {} + end + + it 'second call should give exception' do + Coverage.start + require @class_file.chomp('.rb') + Coverage.result + -> { + Coverage.result + }.should raise_error(RuntimeError, 'coverage measurement is not enabled') + end + + it 'second run should give same result' do + Coverage.start + load @class_file + result1 = Coverage.result + + Coverage.start + load @class_file + result2 = Coverage.result + + result2.should == result1 + end + + it 'second run without load/require should give empty hash' do + Coverage.start + require @class_file.chomp('.rb') + Coverage.result + + Coverage.start + result = Coverage.result + + result.should == {} + end + + it 'does not include the file starting coverage since it is not tracked' do + require @config_file.chomp('.rb') + Coverage.result.should_not include(@config_file) + end + + it 'returns the correct results when eval coverage is enabled' do + Coverage.supported?(:eval).should == true + + Coverage.start(lines: true, eval: true) + require @eval_code_file.chomp('.rb') + result = Coverage.result + + result.should == { + @eval_code_file => { + lines: [1, nil, 1, nil, 1, 1, nil, nil, nil, nil, 1] + } + } + end + + it 'returns the correct results when eval coverage is disabled' do + Coverage.supported?(:eval).should == true + + Coverage.start(lines: true, eval: false) + require @eval_code_file.chomp('.rb') + result = Coverage.result + + result.should == { + @eval_code_file => { + lines: [1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1] + } + } + end + + it "disables coverage measurement when stop option is not specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result + Coverage.running?.should == false + end + + it "disables coverage measurement when stop: true option is specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true) + }.should complain(/warning: stop implies clear/) + + Coverage.running?.should == false + end + + it "does not disable coverage measurement when stop: false option is specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false) + Coverage.running?.should == true + end + + it "does not disable coverage measurement when stop option is not specified but clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(clear: true) + Coverage.running?.should == true + end + + it "does not disable coverage measurement when stop option is not specified but clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(clear: false) + Coverage.running?.should == true + end + + it "disables coverage measurement when stop: true and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: true, clear: true) + Coverage.running?.should == false + end + + it "disables coverage measurement when stop: true and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true, clear: false) + }.should complain(/warning: stop implies clear/) + + Coverage.running?.should == false + end + + it "does not disable coverage measurement when stop: false and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false, clear: true) + Coverage.running?.should == true + end + + it "does not disable coverage measurement when stop: false and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false, clear: false) + Coverage.running?.should == true + end + + it "resets counters (remove them) when stop: true specified but clear option is not specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true) # clears counters + }.should complain(/warning: stop implies clear/) + + Coverage.start + Coverage.peek_result.should == {} + end + + it "resets counters (remove them) when stop: true and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: true, clear: true) # clears counters + + Coverage.start + Coverage.peek_result.should == {} + end + + it "resets counters (remove them) when stop: true and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true, clear: false) # clears counters + }.should complain(/warning: stop implies clear/) + + Coverage.start + Coverage.peek_result.should == {} + end + + it "resets counters (remove them) when both stop and clear options are not specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result # clears counters + + Coverage.start + Coverage.peek_result.should == {} + end + + it "clears counters (sets 0 values) when stop is not specified but clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(clear: true) # clears counters + + Coverage.peek_result.should == { + @class_file => [ + nil, nil, 0, nil, nil, 0, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + end + + it "does not clear counters when stop is not specified but clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + result = Coverage.result(clear: false) # doesn't clear counters + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + + Coverage.peek_result.should == result + end + + it "does not clear counters when stop: false and clear is not specified" do + Coverage.start + require @class_file.chomp('.rb') + + result = Coverage.result(stop: false) # doesn't clear counters + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + + Coverage.peek_result.should == result + end + + it "clears counters (sets 0 values) when stop: false and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false, clear: true) # clears counters + + Coverage.peek_result.should == { + @class_file => [ + nil, nil, 0, nil, nil, 0, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + end + + it "does not clear counters when stop: false and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + result = Coverage.result(stop: false, clear: false) # doesn't clear counters + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + + Coverage.peek_result.should == result + end + + it 'covers 100% lines with begin' do + Coverage.start + require @with_begin_file.chomp('.rb') + result = Coverage.result + + result.should == { + @with_begin_file => [ + nil, 1, nil + ] + } + end +end diff --git a/spec/ruby/library/coverage/running_spec.rb b/spec/ruby/library/coverage/running_spec.rb new file mode 100644 index 0000000000..e9cc7ada5a --- /dev/null +++ b/spec/ruby/library/coverage/running_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'coverage' + +describe 'Coverage.running?' do + it "returns false if coverage is not started" do + Coverage.running?.should == false + end + + it "returns true if coverage is started" do + Coverage.start + Coverage.running?.should == true + Coverage.result + end + + it "returns false if coverage was started and stopped" do + Coverage.start + Coverage.result + Coverage.running?.should == false + end +end diff --git a/spec/ruby/library/coverage/start_spec.rb b/spec/ruby/library/coverage/start_spec.rb new file mode 100644 index 0000000000..c921b85401 --- /dev/null +++ b/spec/ruby/library/coverage/start_spec.rb @@ -0,0 +1,87 @@ +require_relative '../../spec_helper' +require 'coverage' + +describe 'Coverage.start' do + before :each do + Coverage.should_not.running? + end + + after :each do + Coverage.result(stop: true, clear: true) if Coverage.running? + end + + it "enables the coverage measurement" do + Coverage.start + Coverage.should.running? + end + + it "returns nil" do + Coverage.start.should == nil + end + + it 'raises error when repeated Coverage.start call happens' do + Coverage.start + + -> { + Coverage.start + }.should raise_error(RuntimeError, 'coverage measurement is already setup') + end + + it "accepts :all optional argument" do + Coverage.start(:all) + Coverage.should.running? + end + + it "accepts lines: optional keyword argument" do + Coverage.start(lines: true) + Coverage.should.running? + end + + it "accepts branches: optional keyword argument" do + Coverage.start(branches: true) + Coverage.should.running? + end + + it "accepts methods: optional keyword argument" do + Coverage.start(methods: true) + Coverage.should.running? + end + + it "accepts eval: optional keyword argument" do + Coverage.start(eval: true) + Coverage.should.running? + end + + it "accepts oneshot_lines: optional keyword argument" do + Coverage.start(oneshot_lines: true) + Coverage.should.running? + end + + it "ignores unknown keyword arguments" do + Coverage.start(foo: true) + Coverage.should.running? + end + + it "expects a Hash if not passed :all" do + -> { + Coverage.start(42) + }.should raise_error(TypeError, "no implicit conversion of Integer into Hash") + end + + it "does not accept both lines: and oneshot_lines: keyword arguments" do + -> { + Coverage.start(lines: true, oneshot_lines: true) + }.should raise_error(RuntimeError, "cannot enable lines and oneshot_lines simultaneously") + end + + it "enables the coverage measurement if passed options with `false` value" do + Coverage.start(lines: false, branches: false, methods: false, eval: false, oneshot_lines: false) + Coverage.should.running? + end + + it "measures coverage within eval" do + Coverage.start(lines: true, eval: true) + eval("Object.new\n"*3, binding, "test.rb", 1) + Coverage.result["test.rb"].should == {lines: [1, 1, 1]} + end +end diff --git a/spec/ruby/library/coverage/supported_spec.rb b/spec/ruby/library/coverage/supported_spec.rb new file mode 100644 index 0000000000..9226548c1f --- /dev/null +++ b/spec/ruby/library/coverage/supported_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'coverage' + +describe "Coverage.supported?" do + it "returns true or false if coverage measurement is supported for the given mode" do + [true, false].should.include?(Coverage.supported?(:lines)) + [true, false].should.include?(Coverage.supported?(:branches)) + [true, false].should.include?(Coverage.supported?(:methods)) + [true, false].should.include?(Coverage.supported?(:eval)) + end + + it "returns false for not existing modes" do + Coverage.supported?(:foo).should == false + Coverage.supported?(:bar).should == false + end + + it "raise TypeError if argument is not Symbol" do + -> { + Coverage.supported?("lines") + }.should raise_error(TypeError, "wrong argument type String (expected Symbol)") + + -> { + Coverage.supported?([]) + }.should raise_error(TypeError, "wrong argument type Array (expected Symbol)") + + -> { + Coverage.supported?(1) + }.should raise_error(TypeError, "wrong argument type Integer (expected Symbol)") + end +end diff --git a/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb b/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb new file mode 100644 index 0000000000..599e640624 --- /dev/null +++ b/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::BasicWriter#close_on_terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/basicwriter/initialize_spec.rb b/spec/ruby/library/csv/basicwriter/initialize_spec.rb new file mode 100644 index 0000000000..2c13c93f2e --- /dev/null +++ b/spec/ruby/library/csv/basicwriter/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::BasicWriter#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/basicwriter/terminate_spec.rb b/spec/ruby/library/csv/basicwriter/terminate_spec.rb new file mode 100644 index 0000000000..8c53db3f0b --- /dev/null +++ b/spec/ruby/library/csv/basicwriter/terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::BasicWriter#terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/cell/data_spec.rb b/spec/ruby/library/csv/cell/data_spec.rb new file mode 100644 index 0000000000..aa034b0b62 --- /dev/null +++ b/spec/ruby/library/csv/cell/data_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Cell#data" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/cell/initialize_spec.rb b/spec/ruby/library/csv/cell/initialize_spec.rb new file mode 100644 index 0000000000..c9e506676c --- /dev/null +++ b/spec/ruby/library/csv/cell/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Cell#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/fixtures/one_line.csv b/spec/ruby/library/csv/fixtures/one_line.csv new file mode 100644 index 0000000000..d72f2010a8 --- /dev/null +++ b/spec/ruby/library/csv/fixtures/one_line.csv @@ -0,0 +1 @@ +1,2 diff --git a/spec/ruby/library/csv/foreach_spec.rb b/spec/ruby/library/csv/foreach_spec.rb new file mode 100644 index 0000000000..36b3cd152f --- /dev/null +++ b/spec/ruby/library/csv/foreach_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.foreach" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/generate_line_spec.rb b/spec/ruby/library/csv/generate_line_spec.rb new file mode 100644 index 0000000000..656365b109 --- /dev/null +++ b/spec/ruby/library/csv/generate_line_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.generate_line" do + + it "generates an empty string" do + result = CSV.generate_line([]) + result.should == "\n" + end + + it "generates the string 'foo,bar'" do + result = CSV.generate_line(["foo", "bar"]) + result.should == "foo,bar\n" + end + + it "generates the string 'foo;bar'" do + result = CSV.generate_line(["foo", "bar"], col_sep: ?;) + result.should == "foo;bar\n" + end + + it "generates the string 'foo,,bar'" do + result = CSV.generate_line(["foo", nil, "bar"]) + result.should == "foo,,bar\n" + end + + it "generates the string 'foo;;bar'" do + result = CSV.generate_line(["foo", nil, "bar"], col_sep: ?;) + result.should == "foo;;bar\n" + end +end diff --git a/spec/ruby/library/csv/generate_row_spec.rb b/spec/ruby/library/csv/generate_row_spec.rb new file mode 100644 index 0000000000..79dfc34000 --- /dev/null +++ b/spec/ruby/library/csv/generate_row_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.generate_row" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/generate_spec.rb b/spec/ruby/library/csv/generate_spec.rb new file mode 100644 index 0000000000..b45e2eb95b --- /dev/null +++ b/spec/ruby/library/csv/generate_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require 'csv' +require 'tempfile' + +describe "CSV.generate" do + + it "returns CSV string" do + csv_str = CSV.generate do |csv| + csv.add_row [1, 2, 3] + csv << [4, 5, 6] + end + csv_str.should == "1,2,3\n4,5,6\n" + end + + it "accepts a col separator" do + csv_str = CSV.generate(col_sep: ";") do |csv| + csv.add_row [1, 2, 3] + csv << [4, 5, 6] + end + csv_str.should == "1;2;3\n4;5;6\n" + end + + it "appends and returns the argument itself" do + str = +"" + csv_str = CSV.generate(str) do |csv| + csv.add_row [1, 2, 3] + csv << [4, 5, 6] + end + csv_str.should equal str + str.should == "1,2,3\n4,5,6\n" + end +end diff --git a/spec/ruby/library/csv/iobuf/close_spec.rb b/spec/ruby/library/csv/iobuf/close_spec.rb new file mode 100644 index 0000000000..e2fda86080 --- /dev/null +++ b/spec/ruby/library/csv/iobuf/close_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOBuf#close" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/iobuf/initialize_spec.rb b/spec/ruby/library/csv/iobuf/initialize_spec.rb new file mode 100644 index 0000000000..f08e82548a --- /dev/null +++ b/spec/ruby/library/csv/iobuf/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOBuf#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/iobuf/read_spec.rb b/spec/ruby/library/csv/iobuf/read_spec.rb new file mode 100644 index 0000000000..b45334ee2a --- /dev/null +++ b/spec/ruby/library/csv/iobuf/read_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOBuf#read" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/iobuf/terminate_spec.rb b/spec/ruby/library/csv/iobuf/terminate_spec.rb new file mode 100644 index 0000000000..69289e960c --- /dev/null +++ b/spec/ruby/library/csv/iobuf/terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOBuf#terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb b/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb new file mode 100644 index 0000000000..4887ade1a1 --- /dev/null +++ b/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOReader#close_on_terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/ioreader/get_row_spec.rb b/spec/ruby/library/csv/ioreader/get_row_spec.rb new file mode 100644 index 0000000000..5fd2178b1b --- /dev/null +++ b/spec/ruby/library/csv/ioreader/get_row_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOReader#get_row" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/ioreader/initialize_spec.rb b/spec/ruby/library/csv/ioreader/initialize_spec.rb new file mode 100644 index 0000000000..4e8c0964df --- /dev/null +++ b/spec/ruby/library/csv/ioreader/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOReader#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/ioreader/terminate_spec.rb b/spec/ruby/library/csv/ioreader/terminate_spec.rb new file mode 100644 index 0000000000..676cd03a0f --- /dev/null +++ b/spec/ruby/library/csv/ioreader/terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::IOReader#terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/liberal_parsing_spec.rb b/spec/ruby/library/csv/liberal_parsing_spec.rb new file mode 100644 index 0000000000..9878658027 --- /dev/null +++ b/spec/ruby/library/csv/liberal_parsing_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV#liberal_parsing?" do + it "returns true if illegal input is handled" do + csv = CSV.new("", liberal_parsing: true) + csv.should.liberal_parsing? + end + + it "returns false if illegal input is not handled" do + csv = CSV.new("", liberal_parsing: false) + csv.should_not.liberal_parsing? + end + + it "returns false by default" do + csv = CSV.new("") + csv.should_not.liberal_parsing? + end +end diff --git a/spec/ruby/library/csv/open_spec.rb b/spec/ruby/library/csv/open_spec.rb new file mode 100644 index 0000000000..2d310cda3e --- /dev/null +++ b/spec/ruby/library/csv/open_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.open" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/parse_spec.rb b/spec/ruby/library/csv/parse_spec.rb new file mode 100644 index 0000000000..ef5d4ea3ca --- /dev/null +++ b/spec/ruby/library/csv/parse_spec.rb @@ -0,0 +1,93 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.parse" do + + it "parses '' into []" do + result = CSV.parse '' + result.should be_kind_of(Array) + result.should == [] + end + + it "parses '\n' into [[]]" do + result = CSV.parse "\n" + result.should == [[]] + end + + it "parses 'foo' into [['foo']]" do + result = CSV.parse 'foo' + result.should == [['foo']] + end + + it "parses 'foo,bar,baz' into [['foo','bar','baz']]" do + result = CSV.parse 'foo,bar,baz' + result.should == [['foo','bar','baz']] + end + + it "parses 'foo,baz' into [[foo,nil,baz]]" do + result = CSV.parse 'foo,,baz' + result.should == [['foo',nil,'baz']] + end + + it "parses '\nfoo' into [[],['foo']]" do + result = CSV.parse "\nfoo" + result.should == [[],['foo']] + end + + it "parses 'foo\n' into [['foo']]" do + result = CSV.parse "foo\n" + result.should == [['foo']] + end + + it "parses 'foo\nbar' into [['foo'],['bar']]" do + result = CSV.parse "foo\nbar" + result.should == [['foo'],['bar']] + end + + it "parses 'foo,bar\nbaz,quz' into [['foo','bar'],['baz','quz']]" do + result = CSV.parse "foo,bar\nbaz,quz" + result.should == [['foo','bar'],['baz','quz']] + end + + it "parses 'foo,bar'\nbaz' into [['foo','bar'],['baz']]" do + result = CSV.parse "foo,bar\nbaz" + result.should == [['foo','bar'],['baz']] + end + + it "parses 'foo\nbar,baz' into [['foo'],['bar','baz']]" do + result = CSV.parse "foo\nbar,baz" + result.should == [['foo'],['bar','baz']] + end + + it "parses '\n\nbar' into [[],[],'bar']]" do + result = CSV.parse "\n\nbar" + result.should == [[],[],['bar']] + end + + it "parses 'foo' into [['foo']] with a separator of ;" do + result = CSV.parse "foo", col_sep: ?; + result.should == [['foo']] + end + + it "parses 'foo;bar' into [['foo','bar']] with a separator of ;" do + result = CSV.parse "foo;bar", col_sep: ?; + result.should == [['foo','bar']] + end + + it "parses 'foo;bar\nbaz;quz' into [['foo','bar'],['baz','quz']] with a separator of ;" do + result = CSV.parse "foo;bar\nbaz;quz", col_sep: ?; + result.should == [['foo','bar'],['baz','quz']] + end + + it "raises CSV::MalformedCSVError exception if input is illegal" do + -> { + CSV.parse('"quoted" field') + }.should raise_error(CSV::MalformedCSVError) + end + + it "handles illegal input with the liberal_parsing option" do + illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' + result = CSV.parse(illegal_input, liberal_parsing: true) + result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']] + end +end diff --git a/spec/ruby/library/csv/read_spec.rb b/spec/ruby/library/csv/read_spec.rb new file mode 100644 index 0000000000..2e6bb65d56 --- /dev/null +++ b/spec/ruby/library/csv/read_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.read" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/readlines_spec.rb b/spec/ruby/library/csv/readlines_spec.rb new file mode 100644 index 0000000000..14dea34381 --- /dev/null +++ b/spec/ruby/library/csv/readlines_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require 'csv' + +describe "CSV.readlines" do + it "needs to be reviewed for spec completeness" +end + +describe "CSV#readlines" do + it "returns an Array of Array containing each element in a one-line CSV file" do + file = CSV.new "a, b, c" + file.readlines.should == [["a", " b", " c"]] + end + + it "returns an Array of Arrays containing each element in a multi-line CSV file" do + file = CSV.new "a, b, c\nd, e, f" + file.readlines.should == [["a", " b", " c"], ["d", " e", " f"]] + end + + it "returns nil for a missing value" do + file = CSV.new "a,, b, c" + file.readlines.should == [["a", nil, " b", " c"]] + end + + it "raises CSV::MalformedCSVError exception if input is illegal" do + csv = CSV.new('"quoted" field') + -> { csv.readlines }.should raise_error(CSV::MalformedCSVError) + end + + it "handles illegal input with the liberal_parsing option" do + illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' + csv = CSV.new(illegal_input, liberal_parsing: true) + result = csv.readlines + result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']] + end +end diff --git a/spec/ruby/library/csv/streambuf/add_buf_spec.rb b/spec/ruby/library/csv/streambuf/add_buf_spec.rb new file mode 100644 index 0000000000..58c530c500 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/add_buf_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#add_buf" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/buf_size_spec.rb b/spec/ruby/library/csv/streambuf/buf_size_spec.rb new file mode 100644 index 0000000000..1793c8b65e --- /dev/null +++ b/spec/ruby/library/csv/streambuf/buf_size_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#buf_size" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/drop_spec.rb b/spec/ruby/library/csv/streambuf/drop_spec.rb new file mode 100644 index 0000000000..448f0a2196 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/drop_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#drop" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/element_reference_spec.rb b/spec/ruby/library/csv/streambuf/element_reference_spec.rb new file mode 100644 index 0000000000..5a75901830 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/element_reference_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#[]" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/get_spec.rb b/spec/ruby/library/csv/streambuf/get_spec.rb new file mode 100644 index 0000000000..2255e55e91 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/get_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#get" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb b/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb new file mode 100644 index 0000000000..563b8b2d4a --- /dev/null +++ b/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#idx_is_eos?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/initialize_spec.rb b/spec/ruby/library/csv/streambuf/initialize_spec.rb new file mode 100644 index 0000000000..1273c98094 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/is_eos_spec.rb b/spec/ruby/library/csv/streambuf/is_eos_spec.rb new file mode 100644 index 0000000000..a0a3c1e0b0 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/is_eos_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#is_eos?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/read_spec.rb b/spec/ruby/library/csv/streambuf/read_spec.rb new file mode 100644 index 0000000000..cf98c53409 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/read_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#read" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/rel_buf_spec.rb b/spec/ruby/library/csv/streambuf/rel_buf_spec.rb new file mode 100644 index 0000000000..548e347200 --- /dev/null +++ b/spec/ruby/library/csv/streambuf/rel_buf_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#rel_buf" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/streambuf/terminate_spec.rb b/spec/ruby/library/csv/streambuf/terminate_spec.rb new file mode 100644 index 0000000000..247b33184a --- /dev/null +++ b/spec/ruby/library/csv/streambuf/terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StreamBuf#terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/stringreader/get_row_spec.rb b/spec/ruby/library/csv/stringreader/get_row_spec.rb new file mode 100644 index 0000000000..5cc3447061 --- /dev/null +++ b/spec/ruby/library/csv/stringreader/get_row_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StringReader#get_row" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/stringreader/initialize_spec.rb b/spec/ruby/library/csv/stringreader/initialize_spec.rb new file mode 100644 index 0000000000..4e3634847e --- /dev/null +++ b/spec/ruby/library/csv/stringreader/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::StringReader#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/add_row_spec.rb b/spec/ruby/library/csv/writer/add_row_spec.rb new file mode 100644 index 0000000000..2f074b45db --- /dev/null +++ b/spec/ruby/library/csv/writer/add_row_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer#add_row" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/append_spec.rb b/spec/ruby/library/csv/writer/append_spec.rb new file mode 100644 index 0000000000..4e1f6728c2 --- /dev/null +++ b/spec/ruby/library/csv/writer/append_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer#<<" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/close_spec.rb b/spec/ruby/library/csv/writer/close_spec.rb new file mode 100644 index 0000000000..1a87094bb7 --- /dev/null +++ b/spec/ruby/library/csv/writer/close_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer#close" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/create_spec.rb b/spec/ruby/library/csv/writer/create_spec.rb new file mode 100644 index 0000000000..a4514d5578 --- /dev/null +++ b/spec/ruby/library/csv/writer/create_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer.create" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/generate_spec.rb b/spec/ruby/library/csv/writer/generate_spec.rb new file mode 100644 index 0000000000..6ea9161777 --- /dev/null +++ b/spec/ruby/library/csv/writer/generate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer.generate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/initialize_spec.rb b/spec/ruby/library/csv/writer/initialize_spec.rb new file mode 100644 index 0000000000..6bba8f8d0a --- /dev/null +++ b/spec/ruby/library/csv/writer/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/csv/writer/terminate_spec.rb b/spec/ruby/library/csv/writer/terminate_spec.rb new file mode 100644 index 0000000000..77136dd018 --- /dev/null +++ b/spec/ruby/library/csv/writer/terminate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'csv' + +describe "CSV::Writer#terminate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/accessor_spec.rb b/spec/ruby/library/date/accessor_spec.rb new file mode 100644 index 0000000000..74ed0e9c21 --- /dev/null +++ b/spec/ruby/library/date/accessor_spec.rb @@ -0,0 +1,91 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#ajd" do + it "determines the Astronomical Julian day" do + Date.civil(2007, 1, 17).ajd.should == 4908235.to_r / 2 + end +end + +describe "Date#amjd" do + it "determines the Astronomical Modified Julian day" do + Date.civil(2007, 1, 17).amjd.should == 54117 + end +end + +describe "Date#day_fraction" do + it "determines the day fraction" do + Date.civil(2007, 1, 17).day_fraction.should == 0 + end +end + +describe "Date#mjd" do + it "determines the Modified Julian day" do + Date.civil(2007, 1, 17).mjd.should == 54117 + end +end + +describe "Date#ld" do + it "determines the Modified Julian day" do + Date.civil(2007, 1, 17).ld.should == 154958 + end +end + +describe "Date#year" do + it "determines the year" do + Date.civil(2007, 1, 17).year.should == 2007 + end +end + +describe "Date#yday" do + it "determines the day of the year" do + Date.civil(2007, 1, 17).yday.should == 17 + Date.civil(2008, 10, 28).yday.should == 302 + end +end + +describe "Date#mon" do + it "determines the month" do + Date.civil(2007, 1, 17).mon.should == 1 + Date.civil(2008, 10, 28).mon.should == 10 + end +end + +describe "Date#mday" do + it "determines the day of the month" do + Date.civil(2007, 1, 17).mday.should == 17 + Date.civil(2008, 10, 28).mday.should == 28 + end +end + +describe "Date#wday" do + it "determines the week day" do + Date.civil(2007, 1, 17).wday.should == 3 + Date.civil(2008, 10, 26).wday.should == 0 + end +end + +describe "Date#cwyear" do + it "determines the commercial year" do + Date.civil(2007, 1, 17).cwyear.should == 2007 + Date.civil(2008, 10, 28).cwyear.should == 2008 + Date.civil(2007, 12, 31).cwyear.should == 2008 + Date.civil(2010, 1, 1).cwyear.should == 2009 + end +end + +describe "Date#cweek" do + it "determines the commercial week" do + Date.civil(2007, 1, 17).cweek.should == 3 + Date.civil(2008, 10, 28).cweek.should == 44 + Date.civil(2007, 12, 31).cweek.should == 1 + Date.civil(2010, 1, 1).cweek.should == 53 + end +end + +describe "Date#cwday" do + it "determines the commercial week day" do + Date.civil(2007, 1, 17).cwday.should == 3 + Date.civil(2008, 10, 26).cwday.should == 7 + end +end diff --git a/spec/ruby/library/date/add_month_spec.rb b/spec/ruby/library/date/add_month_spec.rb new file mode 100644 index 0000000000..40833f6487 --- /dev/null +++ b/spec/ruby/library/date/add_month_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#>>" do + it "adds the number of months to a Date" do + d = Date.civil(2007,2,27) >> 10 + d.should == Date.civil(2007, 12, 27) + end + + it "sets the day to the last day of a month if the day doesn't exist" do + d = Date.civil(2008,3,31) >> 1 + d.should == Date.civil(2008, 4, 30) + end + + it "returns the day of the reform if date falls within calendar reform" do + calendar_reform_italy = Date.new(1582, 10, 4) + d1 = Date.new(1582, 9, 9) >> 1 + d2 = Date.new(1582, 9, 10) >> 1 + d1.should == calendar_reform_italy + d2.should == calendar_reform_italy + end + + it "raise a TypeError when passed a Symbol" do + -> { Date.civil(2007,2,27) >> :hello }.should raise_error(TypeError) + end + + it "raise a TypeError when passed a String" do + -> { Date.civil(2007,2,27) >> "hello" }.should raise_error(TypeError) + end + + it "raise a TypeError when passed a Date" do + -> { Date.civil(2007,2,27) >> Date.new }.should raise_error(TypeError) + end + + it "raise a TypeError when passed an Object" do + -> { Date.civil(2007,2,27) >> Object.new }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/date/add_spec.rb b/spec/ruby/library/date/add_spec.rb new file mode 100644 index 0000000000..2b9cc62023 --- /dev/null +++ b/spec/ruby/library/date/add_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#+" do + it "adds the number of days to a Date" do + d = Date.civil(2007,2,27) + 10 + d.should == Date.civil(2007, 3, 9) + end + + it "adds a negative number of days to a Date" do + d = Date.civil(2007,2,27).+(-10) + d.should == Date.civil(2007, 2, 17) + end + + it "raises a TypeError when passed a Symbol" do + -> { Date.civil(2007,2,27) + :hello }.should raise_error(TypeError) + end + + it "raises a TypeError when passed a String" do + -> { Date.civil(2007,2,27) + "hello" }.should raise_error(TypeError) + end + + it "raises a TypeError when passed a Date" do + -> { Date.civil(2007,2,27) + Date.new }.should raise_error(TypeError) + end + + it "raises a TypeError when passed an Object" do + -> { Date.civil(2007,2,27) + Object.new }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/date/ajd_spec.rb b/spec/ruby/library/date/ajd_spec.rb new file mode 100644 index 0000000000..10f1302354 --- /dev/null +++ b/spec/ruby/library/date/ajd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#ajd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/ajd_to_amjd_spec.rb b/spec/ruby/library/date/ajd_to_amjd_spec.rb new file mode 100644 index 0000000000..948f4c2236 --- /dev/null +++ b/spec/ruby/library/date/ajd_to_amjd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.ajd_to_amjd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/ajd_to_jd_spec.rb b/spec/ruby/library/date/ajd_to_jd_spec.rb new file mode 100644 index 0000000000..e55ce9f4f2 --- /dev/null +++ b/spec/ruby/library/date/ajd_to_jd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.ajd_to_jd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/amjd_spec.rb b/spec/ruby/library/date/amjd_spec.rb new file mode 100644 index 0000000000..ad7bc14965 --- /dev/null +++ b/spec/ruby/library/date/amjd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#amjd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/amjd_to_ajd_spec.rb b/spec/ruby/library/date/amjd_to_ajd_spec.rb new file mode 100644 index 0000000000..66c26a16a8 --- /dev/null +++ b/spec/ruby/library/date/amjd_to_ajd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.amjd_to_ajd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/append_spec.rb b/spec/ruby/library/date/append_spec.rb new file mode 100644 index 0000000000..4305a00321 --- /dev/null +++ b/spec/ruby/library/date/append_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#<<" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/asctime_spec.rb b/spec/ruby/library/date/asctime_spec.rb new file mode 100644 index 0000000000..67d158cc01 --- /dev/null +++ b/spec/ruby/library/date/asctime_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#asctime" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/boat_spec.rb b/spec/ruby/library/date/boat_spec.rb new file mode 100644 index 0000000000..e4f1b797fc --- /dev/null +++ b/spec/ruby/library/date/boat_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#<=>" do + it "returns 0 when two dates are equal" do + (Date.civil(2000, 04, 06) <=> Date.civil(2000, 04, 06)).should == 0 + end + + it "returns -1 when self is less than another date" do + (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06)).should == -1 + end + + it "returns -1 when self is less than a Numeric" do + (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == -1 + end + + it "returns 1 when self is greater than another date" do + (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06)).should == 1 + end + + it "returns 1 when self is greater than a Numeric" do + (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == 1 + end +end diff --git a/spec/ruby/library/date/case_compare_spec.rb b/spec/ruby/library/date/case_compare_spec.rb new file mode 100644 index 0000000000..87d522ee6a --- /dev/null +++ b/spec/ruby/library/date/case_compare_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#===" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/civil_spec.rb b/spec/ruby/library/date/civil_spec.rb new file mode 100644 index 0000000000..1c780fce56 --- /dev/null +++ b/spec/ruby/library/date/civil_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/civil' +require 'date' + +describe "Date.civil" do + it_behaves_like :date_civil, :civil +end diff --git a/spec/ruby/library/date/commercial_spec.rb b/spec/ruby/library/date/commercial_spec.rb new file mode 100644 index 0000000000..d7fc34d74a --- /dev/null +++ b/spec/ruby/library/date/commercial_spec.rb @@ -0,0 +1,17 @@ +require 'date' +require_relative '../../spec_helper' +require_relative 'shared/commercial' + +describe "Date#commercial" do + + it_behaves_like :date_commercial, :commercial + +end + +# reference: +# October 1582 (the Gregorian calendar, Civil Date) +# S M Tu W Th F S +# 1 2 3 4 15 16 +# 17 18 19 20 21 22 23 +# 24 25 26 27 28 29 30 +# 31 diff --git a/spec/ruby/library/date/commercial_to_jd_spec.rb b/spec/ruby/library/date/commercial_to_jd_spec.rb new file mode 100644 index 0000000000..9b77f1229f --- /dev/null +++ b/spec/ruby/library/date/commercial_to_jd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.commercial_to_jd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/comparison_spec.rb b/spec/ruby/library/date/comparison_spec.rb new file mode 100644 index 0000000000..1a94b9dcd2 --- /dev/null +++ b/spec/ruby/library/date/comparison_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#<=>" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/constants_spec.rb b/spec/ruby/library/date/constants_spec.rb new file mode 100644 index 0000000000..1d18dd1b0c --- /dev/null +++ b/spec/ruby/library/date/constants_spec.rb @@ -0,0 +1,48 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date constants" do + + it "defines JULIAN" do + (Date::JULIAN <=> Date::Infinity.new).should == 0 + end + + it "defines GREGORIAN" do + (Date::GREGORIAN <=> -Date::Infinity.new).should == 0 + end + + it "defines ITALY" do + Date::ITALY.should == 2299161 # 1582-10-15 + end + + it "defines ENGLAND" do + Date::ENGLAND.should == 2361222 # 1752-09-14 + end + + it "defines MONTHNAMES" do + Date::MONTHNAMES.should == [nil] + %w(January February March April May June July + August September October November December) + end + + it "defines DAYNAMES" do + Date::DAYNAMES.should == %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday) + end + + it "defines ABBR_MONTHNAMES" do + Date::ABBR_DAYNAMES.should == %w(Sun Mon Tue Wed Thu Fri Sat) + end + + it "freezes MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYSNAMES" do + [Date::MONTHNAMES, Date::DAYNAMES, Date::ABBR_MONTHNAMES, Date::ABBR_DAYNAMES].each do |ary| + -> { + ary << "Unknown" + }.should raise_error(FrozenError, /frozen/) + ary.compact.each do |name| + -> { + name << "modified" + }.should raise_error(FrozenError, /frozen/) + end + end + end + +end diff --git a/spec/ruby/library/date/conversions_spec.rb b/spec/ruby/library/date/conversions_spec.rb new file mode 100644 index 0000000000..a9a320b0fc --- /dev/null +++ b/spec/ruby/library/date/conversions_spec.rb @@ -0,0 +1,43 @@ +require 'date' +require_relative '../../spec_helper' + + +describe "Date#new_start" do + it "converts a date object into another with a new calendar reform" do + Date.civil(1582, 10, 14, Date::ENGLAND).new_start.should == Date.civil(1582, 10, 24) + Date.civil(1582, 10, 4, Date::ENGLAND).new_start.should == Date.civil(1582, 10, 4) + Date.civil(1582, 10, 15).new_start(Date::ENGLAND).should == Date.civil(1582, 10, 5, Date::ENGLAND) + Date.civil(1752, 9, 14).new_start(Date::ENGLAND).should == Date.civil(1752, 9, 14, Date::ENGLAND) + Date.civil(1752, 9, 13).new_start(Date::ENGLAND).should == Date.civil(1752, 9, 2, Date::ENGLAND) + end +end + +describe "Date#italy" do + it "converts a date object into another with the Italian calendar reform" do + Date.civil(1582, 10, 14, Date::ENGLAND).italy.should == Date.civil(1582, 10, 24) + Date.civil(1582, 10, 4, Date::ENGLAND).italy.should == Date.civil(1582, 10, 4) + end +end + +describe "Date#england" do + it "converts a date object into another with the English calendar reform" do + Date.civil(1582, 10, 15).england.should == Date.civil(1582, 10, 5, Date::ENGLAND) + Date.civil(1752, 9, 14).england.should == Date.civil(1752, 9, 14, Date::ENGLAND) + Date.civil(1752, 9, 13).england.should == Date.civil(1752, 9, 2, Date::ENGLAND) + end +end + +describe "Date#julian" do + it "converts a date object into another with the Julian calendar" do + Date.civil(1582, 10, 15).julian.should == Date.civil(1582, 10, 5, Date::JULIAN) + Date.civil(1752, 9, 14).julian.should == Date.civil(1752, 9, 3, Date::JULIAN) + Date.civil(1752, 9, 13).julian.should == Date.civil(1752, 9, 2, Date::JULIAN) + end +end + +describe "Date#gregorian" do + it "converts a date object into another with the Gregorian calendar" do + Date.civil(1582, 10, 4).gregorian.should == Date.civil(1582, 10, 14, Date::GREGORIAN) + Date.civil(1752, 9, 14).gregorian.should == Date.civil(1752, 9, 14, Date::GREGORIAN) + end +end diff --git a/spec/ruby/library/date/ctime_spec.rb b/spec/ruby/library/date/ctime_spec.rb new file mode 100644 index 0000000000..3faa7c6380 --- /dev/null +++ b/spec/ruby/library/date/ctime_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#ctime" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/cwday_spec.rb b/spec/ruby/library/date/cwday_spec.rb new file mode 100644 index 0000000000..c5a39f277f --- /dev/null +++ b/spec/ruby/library/date/cwday_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#cwday" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/cweek_spec.rb b/spec/ruby/library/date/cweek_spec.rb new file mode 100644 index 0000000000..6f7aab3922 --- /dev/null +++ b/spec/ruby/library/date/cweek_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#cweek" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/cwyear_spec.rb b/spec/ruby/library/date/cwyear_spec.rb new file mode 100644 index 0000000000..a85ee29920 --- /dev/null +++ b/spec/ruby/library/date/cwyear_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#cwyear" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/day_fraction_spec.rb b/spec/ruby/library/date/day_fraction_spec.rb new file mode 100644 index 0000000000..12b873773f --- /dev/null +++ b/spec/ruby/library/date/day_fraction_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#day_fraction" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/day_fraction_to_time_spec.rb b/spec/ruby/library/date/day_fraction_to_time_spec.rb new file mode 100644 index 0000000000..d4741d65ec --- /dev/null +++ b/spec/ruby/library/date/day_fraction_to_time_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.day_fraction_to_time" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/day_spec.rb b/spec/ruby/library/date/day_spec.rb new file mode 100644 index 0000000000..bc727c4717 --- /dev/null +++ b/spec/ruby/library/date/day_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#day" do + it "returns the day" do + d = Date.new(2000, 7, 1).day + d.should == 1 + end +end diff --git a/spec/ruby/library/date/deconstruct_keys_spec.rb b/spec/ruby/library/date/deconstruct_keys_spec.rb new file mode 100644 index 0000000000..b9dd6b8816 --- /dev/null +++ b/spec/ruby/library/date/deconstruct_keys_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require 'date' +date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0' + +describe "Date#deconstruct_keys" do + it "returns whole hash for nil as an argument" do + d = Date.new(2022, 10, 5) + d.deconstruct_keys(nil).should == { year: 2022, month: 10, day: 5, yday: 278, wday: 3 } + end + + it "returns only specified keys" do + d = Date.new(2022, 10, 5) + d.deconstruct_keys([:year, :month]).should == { year: 2022, month: 10 } + end + + it "requires one argument" do + -> { + Date.new(2022, 10, 5).deconstruct_keys + }.should raise_error(ArgumentError) + end + + it "it raises error when argument is neither nil nor array" do + d = Date.new(2022, 10, 5) + + -> { d.deconstruct_keys(1) }.should raise_error(TypeError, "wrong argument type Integer (expected Array or nil)") + -> { d.deconstruct_keys("asd") }.should raise_error(TypeError, "wrong argument type String (expected Array or nil)") + -> { d.deconstruct_keys(:x) }.should raise_error(TypeError, "wrong argument type Symbol (expected Array or nil)") + -> { d.deconstruct_keys({}) }.should raise_error(TypeError, "wrong argument type Hash (expected Array or nil)") + end + + it "returns {} when passed []" do + Date.new(2022, 10, 5).deconstruct_keys([]).should == {} + end + + it "ignores non-Symbol keys" do + Date.new(2022, 10, 5).deconstruct_keys(['year', []]).should == {} + end + + it "ignores not existing Symbol keys" do + Date.new(2022, 10, 5).deconstruct_keys([:year, :a]).should == { year: 2022 } + end +end diff --git a/spec/ruby/library/date/downto_spec.rb b/spec/ruby/library/date/downto_spec.rb new file mode 100644 index 0000000000..84c641ee14 --- /dev/null +++ b/spec/ruby/library/date/downto_spec.rb @@ -0,0 +1,18 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#downto" do + + it "creates earlier dates when passed a negative step" do + ds = Date.civil(2000, 4, 14) + de = Date.civil(2000, 3, 29) + count = 0 + ds.step(de, -1) do |d| + d.should <= ds + d.should >= de + count += 1 + end + count.should == 17 + end + +end diff --git a/spec/ruby/library/date/england_spec.rb b/spec/ruby/library/date/england_spec.rb new file mode 100644 index 0000000000..2e30530c5a --- /dev/null +++ b/spec/ruby/library/date/england_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#england" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/eql_spec.rb b/spec/ruby/library/date/eql_spec.rb new file mode 100644 index 0000000000..a1819cae3a --- /dev/null +++ b/spec/ruby/library/date/eql_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#eql?" do + it "returns true if self is equal to another date" do + Date.civil(2007, 10, 11).eql?(Date.civil(2007, 10, 11)).should be_true + end + + it "returns false if self is not equal to another date" do + Date.civil(2007, 10, 11).eql?(Date.civil(2007, 10, 12)).should be_false + end +end diff --git a/spec/ruby/library/date/format/bag/method_missing_spec.rb b/spec/ruby/library/date/format/bag/method_missing_spec.rb new file mode 100644 index 0000000000..03e4fbcd30 --- /dev/null +++ b/spec/ruby/library/date/format/bag/method_missing_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../../spec_helper' +require 'date' + +describe "Date::Format::Bag#method_missing" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/format/bag/to_hash_spec.rb b/spec/ruby/library/date/format/bag/to_hash_spec.rb new file mode 100644 index 0000000000..76734624b9 --- /dev/null +++ b/spec/ruby/library/date/format/bag/to_hash_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../../spec_helper' +require 'date' + +describe "Date::Format::Bag#to_hash" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/friday_spec.rb b/spec/ruby/library/date/friday_spec.rb new file mode 100644 index 0000000000..3dc040fabe --- /dev/null +++ b/spec/ruby/library/date/friday_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#friday?" do + it "should be friday" do + Date.new(2000, 1, 7).friday?.should be_true + end + + it "should not be friday" do + Date.new(2000, 1, 8).friday?.should be_false + end +end diff --git a/spec/ruby/library/date/gregorian_leap_spec.rb b/spec/ruby/library/date/gregorian_leap_spec.rb new file mode 100644 index 0000000000..c3d25cf90f --- /dev/null +++ b/spec/ruby/library/date/gregorian_leap_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#gregorian_leap?" do + it "returns true if a year is a leap year in the Gregorian calendar" do + Date.gregorian_leap?(2000).should be_true + Date.gregorian_leap?(2004).should be_true + end + + it "returns false if a year is not a leap year in the Gregorian calendar" do + Date.gregorian_leap?(1900).should be_false + Date.gregorian_leap?(1999).should be_false + Date.gregorian_leap?(2002).should be_false + end +end diff --git a/spec/ruby/library/date/gregorian_spec.rb b/spec/ruby/library/date/gregorian_spec.rb new file mode 100644 index 0000000000..ea7ece2ade --- /dev/null +++ b/spec/ruby/library/date/gregorian_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#gregorian?" do + + it "marks a day before the calendar reform as Julian" do + Date.civil(1007, 2, 27).gregorian?.should be_false + Date.civil(1907, 2, 27, Date.civil(1930, 1, 1).jd).gregorian?.should be_false + end + + it "marks a day after the calendar reform as Julian" do + Date.civil(2007, 2, 27).should.gregorian? + Date.civil(1607, 2, 27, Date.civil(1582, 1, 1).jd).gregorian?.should be_true + end + +end diff --git a/spec/ruby/library/date/hash_spec.rb b/spec/ruby/library/date/hash_spec.rb new file mode 100644 index 0000000000..4fb452d486 --- /dev/null +++ b/spec/ruby/library/date/hash_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#hash" do + it "returns the same value for equal dates" do + Date.civil(2004, 7, 12).hash.should == Date.civil(2004, 7, 12).hash + end +end diff --git a/spec/ruby/library/date/infinity/abs_spec.rb b/spec/ruby/library/date/infinity/abs_spec.rb new file mode 100644 index 0000000000..c08189155d --- /dev/null +++ b/spec/ruby/library/date/infinity/abs_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#abs" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/coerce_spec.rb b/spec/ruby/library/date/infinity/coerce_spec.rb new file mode 100644 index 0000000000..75e5ebeab7 --- /dev/null +++ b/spec/ruby/library/date/infinity/coerce_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#coerce" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/comparison_spec.rb b/spec/ruby/library/date/infinity/comparison_spec.rb new file mode 100644 index 0000000000..a9b9d124fd --- /dev/null +++ b/spec/ruby/library/date/infinity/comparison_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#<=>" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/d_spec.rb b/spec/ruby/library/date/infinity/d_spec.rb new file mode 100644 index 0000000000..a5bd2427c7 --- /dev/null +++ b/spec/ruby/library/date/infinity/d_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#d" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/finite_spec.rb b/spec/ruby/library/date/infinity/finite_spec.rb new file mode 100644 index 0000000000..8971c2213e --- /dev/null +++ b/spec/ruby/library/date/infinity/finite_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#finite?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/infinite_spec.rb b/spec/ruby/library/date/infinity/infinite_spec.rb new file mode 100644 index 0000000000..848f538672 --- /dev/null +++ b/spec/ruby/library/date/infinity/infinite_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#infinite?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/nan_spec.rb b/spec/ruby/library/date/infinity/nan_spec.rb new file mode 100644 index 0000000000..b0f5d8ac7a --- /dev/null +++ b/spec/ruby/library/date/infinity/nan_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#nan?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/uminus_spec.rb b/spec/ruby/library/date/infinity/uminus_spec.rb new file mode 100644 index 0000000000..1b1f568103 --- /dev/null +++ b/spec/ruby/library/date/infinity/uminus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#-@" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/uplus_spec.rb b/spec/ruby/library/date/infinity/uplus_spec.rb new file mode 100644 index 0000000000..6a3b2d8442 --- /dev/null +++ b/spec/ruby/library/date/infinity/uplus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#+@" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity/zero_spec.rb b/spec/ruby/library/date/infinity/zero_spec.rb new file mode 100644 index 0000000000..7df5518785 --- /dev/null +++ b/spec/ruby/library/date/infinity/zero_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'date' + +describe "Date::Infinity#zero?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/infinity_spec.rb b/spec/ruby/library/date/infinity_spec.rb new file mode 100644 index 0000000000..721fd76066 --- /dev/null +++ b/spec/ruby/library/date/infinity_spec.rb @@ -0,0 +1,67 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date::Infinity" do + + it "should be able to check whether Infinity is zero" do + i = Date::Infinity.new + i.should_not.zero? + end + + it "should be able to check whether Infinity is finite" do + i1 = Date::Infinity.new + i1.should_not.finite? + i2 = Date::Infinity.new(-1) + i2.should_not.finite? + i3 = Date::Infinity.new(0) + i3.should_not.finite? + end + + it "should be able to check whether Infinity is infinite" do + i1 = Date::Infinity.new + i1.infinite?.should == 1 + i2 = Date::Infinity.new(-1) + i2.infinite?.should == -1 + i3 = Date::Infinity.new(0) + i3.infinite?.should == nil + end + + it "should be able to check whether Infinity is not a number" do + i1 = Date::Infinity.new + i1.should_not.nan? + i2 = Date::Infinity.new(-1) + i2.should_not.nan? + i3 = Date::Infinity.new(0) + i3.should.nan? + end + + it "should be able to compare Infinity objects" do + i1 = Date::Infinity.new + i2 = Date::Infinity.new(-1) + i3 = Date::Infinity.new(0) + i4 = Date::Infinity.new + (i4 <=> i1).should == 0 + (i3 <=> i1).should == -1 + (i2 <=> i1).should == -1 + (i3 <=> i2).should == 1 + end + + it "should be able to return plus Infinity for abs" do + i1 = Date::Infinity.new + i2 = Date::Infinity.new(-1) + i3 = Date::Infinity.new(0) + (i2.abs <=> i1).should == 0 + (i3.abs <=> i1).should == 0 + end + + it "should be able to use -@ and +@ for Date::Infinity" do + (Date::Infinity.new <=> +Date::Infinity.new).should == 0 + (Date::Infinity.new(-1) <=> -Date::Infinity.new).should == 0 + end + + it "should be able to coerce a Date::Infinity object" do + Date::Infinity.new.coerce(1).should == [-1, 1] + Date::Infinity.new(0).coerce(2).should == [0, 0] + Date::Infinity.new(-1).coerce(1.5).should == [1, -1] + end +end diff --git a/spec/ruby/library/date/inspect_spec.rb b/spec/ruby/library/date/inspect_spec.rb new file mode 100644 index 0000000000..81c2cc8003 --- /dev/null +++ b/spec/ruby/library/date/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#inspect" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/iso8601_spec.rb b/spec/ruby/library/date/iso8601_spec.rb new file mode 100644 index 0000000000..af66845a6b --- /dev/null +++ b/spec/ruby/library/date/iso8601_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.iso8601" do + it "parses YYYY-MM-DD into a Date object" do + d = Date.iso8601("2018-01-01") + d.should == Date.civil(2018, 1, 1) + end + + it "parses YYYYMMDD into a Date object" do + d = Date.iso8601("20180715") + d.should == Date.civil(2018, 7, 15) + end + + it "parses a negative Date" do + d = Date.iso8601("-4712-01-01") + d.should == Date.civil(-4712, 1, 1) + end + + it "parses a StringSubclass into a Date object" do + d = Date.iso8601(Class.new(String).new("-4712-01-01")) + d.should == Date.civil(-4712, 1, 1) + end + + it "raises a Date::Error if the argument is a invalid Date" do + -> { + Date.iso8601('invalid') + }.should raise_error(Date::Error, "invalid date") + end + + it "raises a Date::Error when passed a nil" do + -> { + Date.iso8601(nil) + }.should raise_error(Date::Error, "invalid date") + end + + it "raises a TypeError when passed an Object" do + -> { Date.iso8601(Object.new) }.should raise_error(TypeError) + end +end + +describe "Date._iso8601" do + it "returns an empty hash if the argument is a invalid Date" do + h = Date._iso8601('invalid') + h.should == {} + end + + it "returns an empty hash if the argument is nil" do + h = Date._iso8601(nil) + h.should == {} + end + + it "raises a TypeError when passed an Object" do + -> { Date._iso8601(Object.new) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/date/italy_spec.rb b/spec/ruby/library/date/italy_spec.rb new file mode 100644 index 0000000000..9369b05180 --- /dev/null +++ b/spec/ruby/library/date/italy_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#italy" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_spec.rb b/spec/ruby/library/date/jd_spec.rb new file mode 100644 index 0000000000..336b783e8d --- /dev/null +++ b/spec/ruby/library/date/jd_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'shared/jd' +require 'date' + +describe "Date#jd" do + + it "determines the Julian day for a Date object" do + Date.civil(2008, 1, 16).jd.should == 2454482 + end + +end + +describe "Date.jd" do + it_behaves_like :date_jd, :jd +end diff --git a/spec/ruby/library/date/jd_to_ajd_spec.rb b/spec/ruby/library/date/jd_to_ajd_spec.rb new file mode 100644 index 0000000000..f946c46b8a --- /dev/null +++ b/spec/ruby/library/date/jd_to_ajd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_ajd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_to_civil_spec.rb b/spec/ruby/library/date/jd_to_civil_spec.rb new file mode 100644 index 0000000000..13b6e47ee2 --- /dev/null +++ b/spec/ruby/library/date/jd_to_civil_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_civil" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_to_commercial_spec.rb b/spec/ruby/library/date/jd_to_commercial_spec.rb new file mode 100644 index 0000000000..2256b74f2a --- /dev/null +++ b/spec/ruby/library/date/jd_to_commercial_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_commercial" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_to_ld_spec.rb b/spec/ruby/library/date/jd_to_ld_spec.rb new file mode 100644 index 0000000000..5954014f85 --- /dev/null +++ b/spec/ruby/library/date/jd_to_ld_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_ld" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_to_mjd_spec.rb b/spec/ruby/library/date/jd_to_mjd_spec.rb new file mode 100644 index 0000000000..24eb84e171 --- /dev/null +++ b/spec/ruby/library/date/jd_to_mjd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_mjd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_to_ordinal_spec.rb b/spec/ruby/library/date/jd_to_ordinal_spec.rb new file mode 100644 index 0000000000..c7c1704948 --- /dev/null +++ b/spec/ruby/library/date/jd_to_ordinal_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_ordinal" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/jd_to_wday_spec.rb b/spec/ruby/library/date/jd_to_wday_spec.rb new file mode 100644 index 0000000000..27e00b2044 --- /dev/null +++ b/spec/ruby/library/date/jd_to_wday_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.jd_to_wday" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/julian_leap_spec.rb b/spec/ruby/library/date/julian_leap_spec.rb new file mode 100644 index 0000000000..2ef2d65d81 --- /dev/null +++ b/spec/ruby/library/date/julian_leap_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.julian_leap?" do + it "determines whether a year is a leap year in the Julian calendar" do + Date.julian_leap?(1900).should be_true + Date.julian_leap?(2000).should be_true + Date.julian_leap?(2004).should be_true + end + + it "determines whether a year is not a leap year in the Julian calendar" do + Date.julian_leap?(1999).should be_false + Date.julian_leap?(2002).should be_false + end +end diff --git a/spec/ruby/library/date/julian_spec.rb b/spec/ruby/library/date/julian_spec.rb new file mode 100644 index 0000000000..db2629d1e7 --- /dev/null +++ b/spec/ruby/library/date/julian_spec.rb @@ -0,0 +1,16 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#julian?" do + + it "marks a day before the calendar reform as Julian" do + Date.civil(1007, 2, 27).should.julian? + Date.civil(1907, 2, 27, Date.civil(1930, 1, 1).jd).julian?.should be_true + end + + it "marks a day after the calendar reform as Julian" do + Date.civil(2007, 2, 27).should_not.julian? + Date.civil(1607, 2, 27, Date.civil(1582, 1, 1).jd).julian?.should be_false + end + +end diff --git a/spec/ruby/library/date/ld_spec.rb b/spec/ruby/library/date/ld_spec.rb new file mode 100644 index 0000000000..73a47d2382 --- /dev/null +++ b/spec/ruby/library/date/ld_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#ld" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/ld_to_jd_spec.rb b/spec/ruby/library/date/ld_to_jd_spec.rb new file mode 100644 index 0000000000..37abe01449 --- /dev/null +++ b/spec/ruby/library/date/ld_to_jd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.ld_to_jd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/leap_spec.rb b/spec/ruby/library/date/leap_spec.rb new file mode 100644 index 0000000000..674b191c9f --- /dev/null +++ b/spec/ruby/library/date/leap_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#leap?" do + it "needs to be reviewed for spec completeness" +end + +describe "Date.leap?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/mday_spec.rb b/spec/ruby/library/date/mday_spec.rb new file mode 100644 index 0000000000..53f6f98169 --- /dev/null +++ b/spec/ruby/library/date/mday_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#mday" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/minus_month_spec.rb b/spec/ruby/library/date/minus_month_spec.rb new file mode 100644 index 0000000000..470c4d8a76 --- /dev/null +++ b/spec/ruby/library/date/minus_month_spec.rb @@ -0,0 +1,23 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#<<" do + + it "subtracts a number of months from a date" do + d = Date.civil(2007,2,27) << 10 + d.should == Date.civil(2006, 4, 27) + end + + it "returns the last day of a month if the day doesn't exist" do + d = Date.civil(2008,3,31) << 1 + d.should == Date.civil(2008, 2, 29) + end + + it "raises an error on non numeric parameters" do + -> { Date.civil(2007,2,27) << :hello }.should raise_error(TypeError) + -> { Date.civil(2007,2,27) << "hello" }.should raise_error(TypeError) + -> { Date.civil(2007,2,27) << Date.new }.should raise_error(TypeError) + -> { Date.civil(2007,2,27) << Object.new }.should raise_error(TypeError) + end + +end diff --git a/spec/ruby/library/date/minus_spec.rb b/spec/ruby/library/date/minus_spec.rb new file mode 100644 index 0000000000..5a2a29e04a --- /dev/null +++ b/spec/ruby/library/date/minus_spec.rb @@ -0,0 +1,30 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#-" do + + it "subtracts a number of days from a Date" do + d = Date.civil(2007, 5 ,2) - 13 + d.should == Date.civil(2007, 4, 19) + end + + it "subtracts a negative number of days from a Date" do + d = Date.civil(2007, 4, 19).-(-13) + d.should == Date.civil(2007, 5 ,2) + end + + it "computes the difference between two dates" do + (Date.civil(2007,2,27) - Date.civil(2007,2,27)).should == 0 + (Date.civil(2007,2,27) - Date.civil(2007,2,26)).should == 1 + (Date.civil(2006,2,27) - Date.civil(2007,2,27)).should == -365 + (Date.civil(2008,2,27) - Date.civil(2007,2,27)).should == 365 + + end + + it "raises an error for non Numeric arguments" do + -> { Date.civil(2007,2,27) - :hello }.should raise_error(TypeError) + -> { Date.civil(2007,2,27) - "hello" }.should raise_error(TypeError) + -> { Date.civil(2007,2,27) - Object.new }.should raise_error(TypeError) + end + +end diff --git a/spec/ruby/library/date/mjd_spec.rb b/spec/ruby/library/date/mjd_spec.rb new file mode 100644 index 0000000000..6f03af346b --- /dev/null +++ b/spec/ruby/library/date/mjd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#mjd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/mjd_to_jd_spec.rb b/spec/ruby/library/date/mjd_to_jd_spec.rb new file mode 100644 index 0000000000..2009261103 --- /dev/null +++ b/spec/ruby/library/date/mjd_to_jd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.mjd_to_jd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/mon_spec.rb b/spec/ruby/library/date/mon_spec.rb new file mode 100644 index 0000000000..616d72cf88 --- /dev/null +++ b/spec/ruby/library/date/mon_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/month' +require 'date' + +describe "Date#mon" do + it_behaves_like :date_month, :mon +end diff --git a/spec/ruby/library/date/monday_spec.rb b/spec/ruby/library/date/monday_spec.rb new file mode 100644 index 0000000000..14a117b73c --- /dev/null +++ b/spec/ruby/library/date/monday_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#monday?" do + it "should be monday" do + Date.new(2000, 1, 3).monday?.should be_true + end +end diff --git a/spec/ruby/library/date/month_spec.rb b/spec/ruby/library/date/month_spec.rb new file mode 100644 index 0000000000..f493ec8119 --- /dev/null +++ b/spec/ruby/library/date/month_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/month' +require 'date' + +describe "Date#month" do + it_behaves_like :date_month, :month +end diff --git a/spec/ruby/library/date/new_spec.rb b/spec/ruby/library/date/new_spec.rb new file mode 100644 index 0000000000..cb64cabce6 --- /dev/null +++ b/spec/ruby/library/date/new_spec.rb @@ -0,0 +1,7 @@ +require 'date' +require_relative '../../spec_helper' +require_relative 'shared/civil' + +describe "Date.new" do + it_behaves_like :date_civil, :new +end diff --git a/spec/ruby/library/date/new_start_spec.rb b/spec/ruby/library/date/new_start_spec.rb new file mode 100644 index 0000000000..aef78f2320 --- /dev/null +++ b/spec/ruby/library/date/new_start_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#new_start" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/next_day_spec.rb b/spec/ruby/library/date/next_day_spec.rb new file mode 100644 index 0000000000..3b066630e7 --- /dev/null +++ b/spec/ruby/library/date/next_day_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#next_day" do + it "returns the next day" do + d = Date.new(2000, 1, 4).next_day + d.should == Date.new(2000, 1, 5) + end + + it "returns three days later across months" do + d = Date.new(2000, 1, 30).next_day(3) + d.should == Date.new(2000, 2, 2) + end +end diff --git a/spec/ruby/library/date/next_month_spec.rb b/spec/ruby/library/date/next_month_spec.rb new file mode 100644 index 0000000000..6ee664433f --- /dev/null +++ b/spec/ruby/library/date/next_month_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#next_month" do + it "returns the next month" do + d = Date.new(2000, 7, 1).next_month + d.should == Date.new(2000, 8, 1) + end + + it "returns three months later" do + d = Date.new(2000, 7, 1).next_month(3) + d.should == Date.new(2000, 10, 1) + end + + it "returns three months later across years" do + d = Date.new(2000, 12, 1).next_month(3) + d.should == Date.new(2001, 3, 1) + end + + it "returns last day of month two months later" do + d = Date.new(2000, 1, 31).next_month(2) + d.should == Date.new(2000, 3, 31) + end + + it "returns last day of next month when same day does not exist" do + d = Date.new(2001, 1, 30).next_month + d.should == Date.new(2001, 2, 28) + end +end diff --git a/spec/ruby/library/date/next_spec.rb b/spec/ruby/library/date/next_spec.rb new file mode 100644 index 0000000000..8063d6a2e4 --- /dev/null +++ b/spec/ruby/library/date/next_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#next" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/next_year_spec.rb b/spec/ruby/library/date/next_year_spec.rb new file mode 100644 index 0000000000..dda9a44008 --- /dev/null +++ b/spec/ruby/library/date/next_year_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#next_year" do + it "returns the day of the reform if date falls within calendar reform" do + calendar_reform_italy = Date.new(1582, 10, 4) + d1 = Date.new(1581, 10, 9).next_year + d2 = Date.new(1581, 10, 10).next_year + d1.should == calendar_reform_italy + d2.should == calendar_reform_italy + end +end diff --git a/spec/ruby/library/date/ordinal_spec.rb b/spec/ruby/library/date/ordinal_spec.rb new file mode 100644 index 0000000000..ec490fd49c --- /dev/null +++ b/spec/ruby/library/date/ordinal_spec.rb @@ -0,0 +1,7 @@ +require 'date' +require_relative '../../spec_helper' +require_relative 'shared/ordinal' + +describe "Date.ordinal" do + it_behaves_like :date_ordinal, :ordinal +end diff --git a/spec/ruby/library/date/ordinal_to_jd_spec.rb b/spec/ruby/library/date/ordinal_to_jd_spec.rb new file mode 100644 index 0000000000..44f4b3321e --- /dev/null +++ b/spec/ruby/library/date/ordinal_to_jd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.ordinal_to_jd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/parse_spec.rb b/spec/ruby/library/date/parse_spec.rb new file mode 100644 index 0000000000..5ef4f6e9b5 --- /dev/null +++ b/spec/ruby/library/date/parse_spec.rb @@ -0,0 +1,159 @@ +require_relative '../../spec_helper' +require_relative 'shared/parse' +require_relative 'shared/parse_us' +require_relative 'shared/parse_eu' +require 'date' + +describe "Date#parse" do + # The space separator is also different, doesn't work for only numbers + it "parses a day name into a Date object" do + d = Date.parse("friday") + d.should == Date.commercial(d.cwyear, d.cweek, 5) + end + + it "parses a month name into a Date object" do + d = Date.parse("october") + d.should == Date.civil(Date.today.year, 10) + end + + it "parses a month day into a Date object" do + d = Date.parse("5th") + d.should == Date.civil(Date.today.year, Date.today.month, 5) + end + + # Specs using numbers + it "throws an argument error for a single digit" do + ->{ Date.parse("1") }.should raise_error(ArgumentError) + end + + it "parses DD as month day number" do + d = Date.parse("10") + d.should == Date.civil(Date.today.year, Date.today.month, 10) + end + + it "parses DDD as year day number" do + d = Date.parse("100") + if Date.gregorian_leap?(Date.today.year) + d.should == Date.civil(Date.today.year, 4, 9) + else + d.should == Date.civil(Date.today.year, 4, 10) + end + end + + it "parses MMDD as month and day" do + d = Date.parse("1108") + d.should == Date.civil(Date.today.year, 11, 8) + end + + it "parses YYDDD as year and day number in 1969--2068" do + d = Date.parse("10100") + d.should == Date.civil(2010, 4, 10) + end + + it "parses YYMMDD as year, month and day in 1969--2068" do + d = Date.parse("201023") + d.should == Date.civil(2020, 10, 23) + end + + it "parses YYYYDDD as year and day number" do + d = Date.parse("1910100") + d.should == Date.civil(1910, 4, 10) + end + + it "parses YYYYMMDD as year, month and day number" do + d = Date.parse("19101101") + d.should == Date.civil(1910, 11, 1) + end + + it "raises a TypeError trying to parse non-String-like object" do + -> { Date.parse(1) }.should raise_error(TypeError) + -> { Date.parse([]) }.should raise_error(TypeError) + end + + it "coerces using to_str" do + c = Class.new do + attr_accessor :string + def to_str + @string + end + end + o = c.new + o.string = "19101101" + + d = Date.parse(o) + d.should == Date.civil(1910, 11, 1) + + # parse should not modify string value + o.to_str.should == "19101101" + end +end + +describe "Date#parse with '.' separator" do + before :all do + @sep = '.' + end + + it_should_behave_like :date_parse +end + +describe "Date#parse with '/' separator" do + before :all do + @sep = '/' + end + + it_should_behave_like :date_parse +end + +describe "Date#parse with ' ' separator" do + before :all do + @sep = ' ' + end + + it_should_behave_like :date_parse +end + +describe "Date#parse with '/' separator US-style" do + before :all do + @sep = '/' + end + + it_should_behave_like :date_parse_us +end + +describe "Date#parse with '-' separator EU-style" do + before :all do + @sep = '-' + end + + it_should_behave_like :date_parse_eu +end + +describe "Date#parse(.)" do + it "parses YYYY.MM.DD into a Date object" do + d = Date.parse("2007.10.01") + d.year.should == 2007 + d.month.should == 10 + d.day.should == 1 + end + + it "parses DD.MM.YYYY into a Date object" do + d = Date.parse("10.01.2007") + d.year.should == 2007 + d.month.should == 1 + d.day.should == 10 + end + + it "parses YY.MM.DD into a Date object using the year 20YY" do + d = Date.parse("10.01.07") + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end + + it "parses YY.MM.DD using the year digits as 20YY when given true as additional argument" do + d = Date.parse("10.01.07", true) + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end +end diff --git a/spec/ruby/library/date/plus_spec.rb b/spec/ruby/library/date/plus_spec.rb new file mode 100644 index 0000000000..0cb99fd4ca --- /dev/null +++ b/spec/ruby/library/date/plus_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#+" do + before :all do + @date = Date.civil(2000, 1, 1) + end + + it "returns a new Date object that is n days later than the current one" do + (@date + 31).should == Date.civil(2000, 2, 1) + end + + it "accepts a negative argument and returns a new Date that is earlier than the current one" do + (@date + -1).should == Date.civil(1999, 12, 31) + end + + it "raises TypeError if argument is not Numeric" do + -> { Date.today + Date.today }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/date/prev_day_spec.rb b/spec/ruby/library/date/prev_day_spec.rb new file mode 100644 index 0000000000..cce24da875 --- /dev/null +++ b/spec/ruby/library/date/prev_day_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#prev_day" do + it "returns previous day" do + d = Date.new(2000, 7, 2).prev_day + d.should == Date.new(2000, 7, 1) + end + + it "returns three days ago across months" do + d = Date.new(2000, 7, 2).prev_day(3) + d.should == Date.new(2000, 6, 29) + end +end diff --git a/spec/ruby/library/date/prev_month_spec.rb b/spec/ruby/library/date/prev_month_spec.rb new file mode 100644 index 0000000000..3d0d1d437d --- /dev/null +++ b/spec/ruby/library/date/prev_month_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#prev_month" do + it "returns previous month" do + d = Date.new(2000, 9, 1).prev_month + d.should == Date.new(2000, 8, 1) + end + + it "returns three months ago" do + d = Date.new(2000, 10, 1).prev_month(3) + d.should == Date.new(2000, 7, 1) + end + + it "returns three months ago across years" do + d = Date.new(2000, 1, 1).prev_month(3) + d.should == Date.new(1999, 10, 1) + end + + it "returns last day of month two months ago" do + d = Date.new(2000, 3, 31).prev_month(2) + d.should == Date.new(2000, 1, 31) + end + + it "returns last day of previous month when same day does not exist" do + d = Date.new(2001, 3, 30).prev_month + d.should == Date.new(2001, 2, 28) + end +end diff --git a/spec/ruby/library/date/prev_year_spec.rb b/spec/ruby/library/date/prev_year_spec.rb new file mode 100644 index 0000000000..ba06dd198b --- /dev/null +++ b/spec/ruby/library/date/prev_year_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#prev_year" do + it "returns the day of the reform if date falls within calendar reform" do + calendar_reform_italy = Date.new(1582, 10, 4) + d1 = Date.new(1583, 10, 9).prev_year + d2 = Date.new(1583, 10, 10).prev_year + d1.should == calendar_reform_italy + d2.should == calendar_reform_italy + end +end diff --git a/spec/ruby/library/date/relationship_spec.rb b/spec/ruby/library/date/relationship_spec.rb new file mode 100644 index 0000000000..979516e164 --- /dev/null +++ b/spec/ruby/library/date/relationship_spec.rb @@ -0,0 +1,20 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#===" do + + it "returns 0 when comparing two equal dates" do + (Date.civil(2000, 04, 06) <=> Date.civil(2000, 04, 06)).should == 0 + end + + it "computes the difference between two dates" do + (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06)).should == -1 + (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06)).should == 1 + end + + it "compares to another numeric" do + (Date.civil(2000, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == -1 + (Date.civil(2001, 04, 05) <=> Date.civil(2000, 04, 06).jd).should == 1 + end + +end diff --git a/spec/ruby/library/date/rfc3339_spec.rb b/spec/ruby/library/date/rfc3339_spec.rb new file mode 100644 index 0000000000..a8711d47b2 --- /dev/null +++ b/spec/ruby/library/date/rfc3339_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.rfc3339" do + it "needs to be reviewed for spec completeness" +end + +describe "Date._rfc3339" do + it "returns an empty hash if the argument is a invalid Date" do + h = Date._rfc3339('invalid') + h.should == {} + end +end diff --git a/spec/ruby/library/date/right_shift_spec.rb b/spec/ruby/library/date/right_shift_spec.rb new file mode 100644 index 0000000000..bd7de0e3d5 --- /dev/null +++ b/spec/ruby/library/date/right_shift_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#>>" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/saturday_spec.rb b/spec/ruby/library/date/saturday_spec.rb new file mode 100644 index 0000000000..1527b71d00 --- /dev/null +++ b/spec/ruby/library/date/saturday_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#saturday?" do + it "should be saturday" do + Date.new(2000, 1, 1).saturday?.should be_true + end +end diff --git a/spec/ruby/library/date/shared/civil.rb b/spec/ruby/library/date/shared/civil.rb new file mode 100644 index 0000000000..bbed4a8866 --- /dev/null +++ b/spec/ruby/library/date/shared/civil.rb @@ -0,0 +1,57 @@ +describe :date_civil, shared: true do + it "creates a Date for -4712 by default" do + # the #chomp calls are necessary because of RSpec + d = Date.send(@method) + d.year.should == -4712 + d.month.should == 1 + d.day.should == 1 + d.should.julian? + d.jd.should == 0 + end + + it "creates a date with arguments" do + d = Date.send(@method, 2000, 3, 5) + d.year.should == 2000 + d.month.should == 3 + d.day.should == 5 + d.should_not.julian? + d.jd.should == 2451609 + + # Should also work with years far in the past and future + + d = Date.send(@method, -9000, 7, 5) + d.year.should == -9000 + d.month.should == 7 + d.day.should == 5 + d.should.julian? + d.jd.should == -1566006 + + d = Date.send(@method, 9000, 10, 14) + d.year.should == 9000 + d.month.should == 10 + d.day.should == 14 + d.should_not.julian? + d.jd.should == 5008529 + + end + + it "doesn't create dates for invalid arguments" do + -> { Date.send(@method, 2000, 13, 31) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2000, 12, 32) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2000, 2, 30) }.should raise_error(ArgumentError) + -> { Date.send(@method, 1900, 2, 29) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2000, 2, 29) }.should_not raise_error(ArgumentError) + + -> { Date.send(@method, 1582, 10, 14) }.should raise_error(ArgumentError) + -> { Date.send(@method, 1582, 10, 15) }.should_not raise_error(ArgumentError) + + end + + it "creates a Date for different calendar reform dates" do + d1 = Date.send(@method, 1582, 10, 4) + d1.succ.day.should == 15 + + d2 = Date.send(@method, 1582, 10, 4, Date::ENGLAND) + d2.succ.day.should == 5 + end +end diff --git a/spec/ruby/library/date/shared/commercial.rb b/spec/ruby/library/date/shared/commercial.rb new file mode 100644 index 0000000000..39c9af47b6 --- /dev/null +++ b/spec/ruby/library/date/shared/commercial.rb @@ -0,0 +1,39 @@ +describe :date_commercial, shared: true do + it "creates a Date for Julian Day Number day 0 by default" do + d = Date.send(@method) + d.year.should == -4712 + d.month.should == 1 + d.day.should == 1 + end + + it "creates a Date for the monday in the year and week given" do + d = Date.send(@method, 2000, 1) + d.year.should == 2000 + d.month.should == 1 + d.day.should == 3 + d.cwday.should == 1 + end + + it "creates a Date for the correct day given the year, week and day number" do + d = Date.send(@method, 2004, 1, 1) + d.year.should == 2003 + d.month.should == 12 + d.day.should == 29 + d.cwday.should == 1 + d.cweek.should == 1 + d.cwyear.should == 2004 + end + + it "creates only Date objects for valid weeks" do + -> { Date.send(@method, 2004, 53, 1) }.should_not raise_error(ArgumentError) + -> { Date.send(@method, 2004, 53, 0) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2004, 53, 8) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2004, 54, 1) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2004, 0, 1) }.should raise_error(ArgumentError) + + -> { Date.send(@method, 2003, 52, 1) }.should_not raise_error(ArgumentError) + -> { Date.send(@method, 2003, 53, 1) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2003, 52, 0) }.should raise_error(ArgumentError) + -> { Date.send(@method, 2003, 52, 8) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/date/shared/jd.rb b/spec/ruby/library/date/shared/jd.rb new file mode 100644 index 0000000000..511557b4f7 --- /dev/null +++ b/spec/ruby/library/date/shared/jd.rb @@ -0,0 +1,14 @@ +describe :date_jd, shared: true do + it "constructs a Date object if passed a Julian day" do + Date.send(@method, 2454482).should == Date.civil(2008, 1, 16) + end + + it "returns a Date object representing Julian day 0 (-4712-01-01) if no arguments passed" do + Date.send(@method).should == Date.civil(-4712, 1, 1) + end + + it "constructs a Date object if passed a negative number" do + Date.send(@method, -1).should == Date.civil(-4713, 12, 31) + end + +end diff --git a/spec/ruby/library/date/shared/month.rb b/spec/ruby/library/date/shared/month.rb new file mode 100644 index 0000000000..5fcb2cbeb0 --- /dev/null +++ b/spec/ruby/library/date/shared/month.rb @@ -0,0 +1,6 @@ +describe :date_month, shared: true do + it "returns the month" do + m = Date.new(2000, 7, 1).send(@method) + m.should == 7 + end +end diff --git a/spec/ruby/library/date/shared/ordinal.rb b/spec/ruby/library/date/shared/ordinal.rb new file mode 100644 index 0000000000..4b182d5a25 --- /dev/null +++ b/spec/ruby/library/date/shared/ordinal.rb @@ -0,0 +1,22 @@ +# reference: +# October 1582 (the Gregorian calendar, Civil Date) +# S M Tu W Th F S +# 1 2 3 4 15 16 +# 17 18 19 20 21 22 23 +# 24 25 26 27 28 29 30 +# 31 + +describe :date_ordinal, shared: true do + it "constructs a Date object from an ordinal date" do + # October 1582 (the Gregorian calendar, Ordinal Date) + # S M Tu W Th F S + # 274 275 276 277 278 279 + # 280 281 282 283 284 285 286 + # 287 288 289 290 291 292 293 + # 294 + Date.send(@method, 1582, 274).should == Date.civil(1582, 10, 1) + Date.send(@method, 1582, 277).should == Date.civil(1582, 10, 4) + Date.send(@method, 1582, 278).should == Date.civil(1582, 10, 15) + Date.send(@method, 1582, 287, Date::ENGLAND).should == Date.civil(1582, 10, 14, Date::ENGLAND) + end +end diff --git a/spec/ruby/library/date/shared/parse.rb b/spec/ruby/library/date/shared/parse.rb new file mode 100644 index 0000000000..40af908386 --- /dev/null +++ b/spec/ruby/library/date/shared/parse.rb @@ -0,0 +1,54 @@ +describe :date_parse, shared: true do + it "can parse a mmm-YYYY string into a Date object" do + d = Date.parse("feb#{@sep}2008") + d.year.should == 2008 + d.month.should == 2 + d.day.should == 1 + end + + it "can parse a 'DD mmm YYYY' string into a Date object" do + d = Date.parse("23#{@sep}feb#{@sep}2008") + d.year.should == 2008 + d.month.should == 2 + d.day.should == 23 + end + + it "can parse a 'DD mmm YYYY' string into a Date object" do + d = Date.parse("23#{@sep}feb#{@sep}2008") + d.year.should == 2008 + d.month.should == 2 + d.day.should == 23 + end + + it "can parse a 'YYYY mmm DD' string into a Date object" do + d = Date.parse("2008#{@sep}feb#{@sep}23") + d.year.should == 2008 + d.month.should == 2 + d.day.should == 23 + end + + it "can parse a month name and day into a Date object" do + d = Date.parse("november#{@sep}5th") + d.should == Date.civil(Date.today.year, 11, 5) + end + + it "can parse a month name, day and year into a Date object" do + d = Date.parse("november#{@sep}5th#{@sep}2005") + d.should == Date.civil(2005, 11, 5) + end + + it "can parse a year, month name and day into a Date object" do + d = Date.parse("2005#{@sep}november#{@sep}5th") + d.should == Date.civil(2005, 11, 5) + end + + it "can parse a day, month name and year into a Date object" do + d = Date.parse("5th#{@sep}november#{@sep}2005") + d.should == Date.civil(2005, 11, 5) + end + + it "can handle negative year numbers" do + d = Date.parse("5th#{@sep}november#{@sep}-2005") + d.should == Date.civil(-2005, 11, 5) + end +end diff --git a/spec/ruby/library/date/shared/parse_eu.rb b/spec/ruby/library/date/shared/parse_eu.rb new file mode 100644 index 0000000000..3819524a57 --- /dev/null +++ b/spec/ruby/library/date/shared/parse_eu.rb @@ -0,0 +1,37 @@ +describe :date_parse_eu, shared: true do + # The - separator let's it work like European format, so it as a different spec + it "can parse a YYYY-MM-DD string into a Date object" do + d = Date.parse("2007#{@sep}10#{@sep}01") + d.year.should == 2007 + d.month.should == 10 + d.day.should == 1 + end + + it "can parse a DD-MM-YYYY string into a Date object" do + d = Date.parse("10#{@sep}01#{@sep}2007") + d.year.should == 2007 + d.month.should == 1 + d.day.should == 10 + end + + it "can parse a YY-MM-DD string into a Date object" do + d = Date.parse("10#{@sep}01#{@sep}07") + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end + + it "can parse a YY-MM-DD string into a Date object NOT using the year digits as 20XX" do + d = Date.parse("10#{@sep}01#{@sep}07", false) + d.year.should == 10 + d.month.should == 1 + d.day.should == 7 + end + + it "can parse a YY-MM-DD string into a Date object using the year digits as 20XX" do + d = Date.parse("10#{@sep}01#{@sep}07", true) + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end +end diff --git a/spec/ruby/library/date/shared/parse_us.rb b/spec/ruby/library/date/shared/parse_us.rb new file mode 100644 index 0000000000..17e2fc96c1 --- /dev/null +++ b/spec/ruby/library/date/shared/parse_us.rb @@ -0,0 +1,36 @@ +describe :date_parse_us, shared: true do + it "parses a YYYY#{@sep}MM#{@sep}DD string into a Date object" do + d = Date.parse("2007#{@sep}10#{@sep}01") + d.year.should == 2007 + d.month.should == 10 + d.day.should == 1 + end + + it "parses a DD#{@sep}MM#{@sep}YYYY string into a Date object" do + d = Date.parse("10#{@sep}01#{@sep}2007") + d.year.should == 2007 + d.month.should == 1 + d.day.should == 10 + end + + it "parses a YY#{@sep}MM#{@sep}DD string into a Date object" do + d = Date.parse("10#{@sep}01#{@sep}07") + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end + + it "parses a YY#{@sep}MM#{@sep}DD string into a Date object NOT using the year digits as 20XX" do + d = Date.parse("10#{@sep}01#{@sep}07", false) + d.year.should == 10 + d.month.should == 1 + d.day.should == 7 + end + + it "parses a YY#{@sep}MM#{@sep}DD string into a Date object using the year digits as 20XX" do + d = Date.parse("10#{@sep}01#{@sep}07", true) + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end +end diff --git a/spec/ruby/library/date/shared/valid_civil.rb b/spec/ruby/library/date/shared/valid_civil.rb new file mode 100644 index 0000000000..545c207bbe --- /dev/null +++ b/spec/ruby/library/date/shared/valid_civil.rb @@ -0,0 +1,36 @@ +describe :date_valid_civil?, shared: true do + + # reference: + # October 1582 (the Gregorian calendar, Civil Date) + # S M Tu W Th F S + # 1 2 3 4 15 16 + # 17 18 19 20 21 22 23 + # 24 25 26 27 28 29 30 + # 31 + + it "returns true if it is a valid civil date" do + Date.send(@method, 1582, 10, 15).should be_true + Date.send(@method, 1582, 10, 14, Date::ENGLAND).should be_true + end + + it "returns false if it is not a valid civil date" do + Date.send(@method, 1582, 10, 14).should == false + end + + it "handles negative months and days" do + # October 1582 (the Gregorian calendar, Civil Date) + # S M Tu W Th F S + # -21 -20 -19 -18 -17 -16 + # -15 -14 -13 -12 -11 -10 -9 + # -8 -7 -6 -5 -4 -3 -2 + # -1 + Date.send(@method, 1582, -3, -22).should be_false + Date.send(@method, 1582, -3, -21).should be_true + Date.send(@method, 1582, -3, -18).should be_true + Date.send(@method, 1582, -3, -17).should be_true + + Date.send(@method, 2007, -11, -10).should be_true + Date.send(@method, 2008, -11, -10).should be_true + end + +end diff --git a/spec/ruby/library/date/shared/valid_commercial.rb b/spec/ruby/library/date/shared/valid_commercial.rb new file mode 100644 index 0000000000..117dfe1d3d --- /dev/null +++ b/spec/ruby/library/date/shared/valid_commercial.rb @@ -0,0 +1,34 @@ +describe :date_valid_commercial?, shared: true do + + it "returns true if it is a valid commercial date" do + # October 1582 (the Gregorian calendar, Commercial Date) + # M Tu W Th F Sa Su + # 39: 1 2 3 4 5 6 7 + # 40: 1 2 3 4 5 6 7 + # 41: 1 2 3 4 5 6 7 + Date.send(@method, 1582, 39, 4).should be_true + Date.send(@method, 1582, 39, 5).should be_true + Date.send(@method, 1582, 41, 4).should be_true + Date.send(@method, 1582, 41, 5).should be_true + Date.send(@method, 1582, 41, 4, Date::ENGLAND).should be_true + Date.send(@method, 1752, 37, 4, Date::ENGLAND).should be_true + end + + it "returns false it is not a valid commercial date" do + Date.send(@method, 1999, 53, 1).should be_false + end + + it "handles negative week and day numbers" do + # October 1582 (the Gregorian calendar, Commercial Date) + # M Tu W Th F Sa Su + # -12: -7 -6 -5 -4 -3 -2 -1 + # -11: -7 -6 -5 -4 -3 -2 -1 + # -10: -7 -6 -5 -4 -3 -2 -1 + Date.send(@method, 1582, -12, -4).should be_true + Date.send(@method, 1582, -12, -3).should be_true + Date.send(@method, 2007, -44, -2).should be_true + Date.send(@method, 2008, -44, -2).should be_true + Date.send(@method, 1999, -53, -1).should be_false + end + +end diff --git a/spec/ruby/library/date/shared/valid_jd.rb b/spec/ruby/library/date/shared/valid_jd.rb new file mode 100644 index 0000000000..e474dfb450 --- /dev/null +++ b/spec/ruby/library/date/shared/valid_jd.rb @@ -0,0 +1,20 @@ +describe :date_valid_jd?, shared: true do + it "returns true if passed a number value" do + Date.send(@method, -100).should be_true + Date.send(@method, 100.0).should be_true + Date.send(@method, 2**100).should be_true + Date.send(@method, Rational(1,2)).should be_true + end + + it "returns false if passed nil" do + Date.send(@method, nil).should be_false + end + + it "returns false if passed symbol" do + Date.send(@method, :number).should be_false + end + + it "returns false if passed false" do + Date.send(@method, false).should be_false + end +end diff --git a/spec/ruby/library/date/shared/valid_ordinal.rb b/spec/ruby/library/date/shared/valid_ordinal.rb new file mode 100644 index 0000000000..1ed961be23 --- /dev/null +++ b/spec/ruby/library/date/shared/valid_ordinal.rb @@ -0,0 +1,26 @@ +describe :date_valid_ordinal?, shared: true do + it "determines if the date is a valid ordinal date" do + # October 1582 (the Gregorian calendar, Ordinal Date) + # S M Tu W Th F S + # 274 275 276 277 278 279 + # 280 281 282 283 284 285 286 + # 287 288 289 290 291 292 293 + # 294 + Date.send(@method, 1582, 277).should == true + Date.send(@method, 1582, 278).should == true + Date.send(@method, 1582, 287).should == true + Date.send(@method, 1582, 288).should == true + end + + it "handles negative day numbers" do + # October 1582 (the Gregorian calendar, Ordinal Date) + # S M Tu W Th F S + # -82 -81 -80 -79 -78 -77 + # -76 -75 -74 -73 -72 -71 -70 + # -69 -68 -67 -66 -65 -64 -63 + # -62 + Date.send(@method, 1582, -79).should == true + Date.send(@method, 1582, -78).should == true + Date.send(@method, 2007, -100).should == true + end +end diff --git a/spec/ruby/library/date/start_spec.rb b/spec/ruby/library/date/start_spec.rb new file mode 100644 index 0000000000..8ba272a7f3 --- /dev/null +++ b/spec/ruby/library/date/start_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#start" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/step_spec.rb b/spec/ruby/library/date/step_spec.rb new file mode 100644 index 0000000000..6bbd671840 --- /dev/null +++ b/spec/ruby/library/date/step_spec.rb @@ -0,0 +1,56 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#step" do + + it "steps forward in time" do + ds = Date.civil(2008, 10, 11) + de = Date.civil(2008, 9, 29) + count = 0 + de.step(ds) do |d| + d.should <= ds + d.should >= de + count += 1 + end + count.should == 13 + + count = 0 + de.step(ds, 5) do |d| + d.should <= ds + d.should >= de + count += 1 + end + count.should == 3 + + count = 0 + ds.step(de) do |d|; count += 1; end + count.should == 0 + + end + + it "steps backward in time" do + ds = Date.civil(2000, 4, 14) + de = Date.civil(2000, 3, 29) + count = 0 + ds.step(de, -1) do |d| + d.should <= ds + d.should >= de + count += 1 + end + count.should == 17 + + count = 0 + ds.step(de, -5) do |d| + d.should <= ds + d.should >= de + count += 1 + end + count.should == 4 + + count = 0 + de.step(ds, -1) do |d|; count += 1; end + count.should == 0 + + end + +end diff --git a/spec/ruby/library/date/strftime_spec.rb b/spec/ruby/library/date/strftime_spec.rb new file mode 100644 index 0000000000..1b93a8d1b2 --- /dev/null +++ b/spec/ruby/library/date/strftime_spec.rb @@ -0,0 +1,41 @@ +require_relative "../../spec_helper" +require 'date' +require_relative '../../shared/time/strftime_for_date' +date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0' + +describe "Date#strftime" do + before :all do + @new_date = -> y, m, d { Date.civil(y,m,d) } + + @date = Date.civil(2000, 4, 9) + end + + it_behaves_like :strftime_date, :strftime + + # Differences with Time + it "should be able to print the date with no argument" do + @date.strftime.should == "2000-04-09" + @date.strftime.should == @date.to_s + end + + # %Z is %:z for Date/DateTime + it "should be able to show the timezone with a : separator" do + @date.strftime("%Z").should == "+00:00" + end + + it "should be able to show the commercial week" do + @date.strftime("%v").should == " 9-APR-2000" + @date.strftime("%v").should != @date.strftime('%e-%b-%Y') + end + + # additional conversion specifiers only in Date/DateTime + it 'shows the number of milliseconds since epoch' do + DateTime.new(1970, 1, 1).strftime('%Q').should == "0" + @date.strftime("%Q").should == "955238400000" + end + + it "should be able to show a full notation" do + @date.strftime("%+").should == "Sun Apr 9 00:00:00 +00:00 2000" + @date.strftime("%+").should == @date.strftime('%a %b %e %H:%M:%S %Z %Y') + end +end diff --git a/spec/ruby/library/date/strptime_spec.rb b/spec/ruby/library/date/strptime_spec.rb new file mode 100644 index 0000000000..c90721751e --- /dev/null +++ b/spec/ruby/library/date/strptime_spec.rb @@ -0,0 +1,149 @@ +require 'date' +require_relative '../../spec_helper' + +describe "Date#strptime" do + + it "returns January 1, 4713 BCE when given no arguments" do + Date.strptime.should == Date.civil(-4712, 1, 1) + end + + it "uses the default format when not given a date format" do + Date.strptime("2000-04-06").should == Date.civil(2000, 4, 6) + Date.civil(2000, 4, 6).strftime.should == Date.civil(2000, 4, 6).to_s + end + + it "parses a full day name" do + d = Date.today + expected_date = Date.commercial(d.cwyear, d.cweek, 4) + # strptime assumed week that start on sunday, not monday + expected_date += 7 if d.cwday == 7 + Date.strptime("Thursday", "%A").should == expected_date + end + + it "parses a short day name" do + d = Date.today + expected_date = Date.commercial(d.cwyear, d.cweek, 4) + # strptime assumed week that start on sunday, not monday + expected_date += 7 if d.cwday == 7 + Date.strptime("Thu", "%a").should == expected_date + end + + it "parses a full month name" do + d = Date.today + Date.strptime("April", "%B").should == Date.civil(d.year, 4, 1) + end + + it "parses a short month name" do + d = Date.today + Date.strptime("Apr", "%b").should == Date.civil(d.year, 4, 1) + Date.strptime("Apr", "%h").should == Date.civil(d.year, 4, 1) + end + + it "parses a century" do + Date.strptime("06 20", "%y %C").should == Date.civil(2006, 1, 1) + end + + it "parses a month day with leading zeroes" do + d = Date.today + Date.strptime("06", "%d").should == Date.civil(d.year, d.month, 6) + end + + it "parses a month day with leading spaces" do + d = Date.today + Date.strptime(" 6", "%e").should == Date.civil(d.year, d.month, 6) + end + + it "parses a commercial year with leading zeroes" do + Date.strptime("2000", "%G").should == Date.civil(2000, 1, 3) + Date.strptime("2002", "%G").should == Date.civil(2001, 12, 31) + end + + it "parses a commercial year with only two digits" do + Date.strptime("68", "%g").should == Date.civil(2068, 1, 2) + Date.strptime("69", "%g").should == Date.civil(1968, 12, 30) + end + + it "parses a year day with leading zeroes" do + d = Date.today + if Date.gregorian_leap?(Date.today.year) + Date.strptime("097", "%j").should == Date.civil(d.year, 4, 6) + else + Date.strptime("097", "%j").should == Date.civil(d.year, 4, 7) + end + end + + it "parses a month with leading zeroes" do + d = Date.today + Date.strptime("04", "%m").should == Date.civil(d.year, 4, 1) + end + + it "parses a week number for a week starting on Sunday" do + Date.strptime("2010/1", "%Y/%U").should == Date.civil(2010, 1, 3) + end + + # See http://redmine.ruby-lang.org/repositories/diff/ruby-19?rev=24500 + it "parses a week number for a week starting on Monday" do + Date.strptime("2010/1", "%Y/%W").should == Date.civil(2010, 1, 4) + end + + it "parses a commercial week day" do + Date.strptime("2008 1", "%G %u").should == Date.civil(2007, 12, 31) + end + + it "parses a commercial week" do + d = Date.commercial(Date.today.cwyear,1,1) + Date.strptime("1", "%V").should == d + Date.strptime("15", "%V").should == Date.commercial(d.cwyear, 15, 1) + end + + it "parses a week day" do + Date.strptime("2007 4", "%Y %w").should == Date.civil(2007, 1, 4) + end + + it "parses a year in YYYY format" do + Date.strptime("2007", "%Y").should == Date.civil(2007, 1, 1) + end + + it "parses a year in YY format" do + Date.strptime("00", "%y").should == Date.civil(2000, 1, 1) + end + + ############################ + # Specs that combine stuff # + ############################ + + it "parses a full date" do + Date.strptime("Thu Apr 6 00:00:00 2000", "%c").should == Date.civil(2000, 4, 6) + Date.strptime("Thu Apr 6 00:00:00 2000", "%a %b %e %H:%M:%S %Y").should == Date.civil(2000, 4, 6) + end + + it "parses a date with slashes" do + Date.strptime("04/06/00", "%D").should == Date.civil(2000, 4, 6) + Date.strptime("04/06/00", "%m/%d/%y").should == Date.civil(2000, 4, 6) + end + + it "parses a date given as YYYY-MM-DD" do + Date.strptime("2000-04-06", "%F").should == Date.civil(2000, 4, 6) + Date.strptime("2000-04-06", "%Y-%m-%d").should == Date.civil(2000, 4, 6) + end + + it "parses a commercial week" do + Date.strptime(" 9-Apr-2000", "%v").should == Date.civil(2000, 4, 9) + Date.strptime(" 9-Apr-2000", "%e-%b-%Y").should == Date.civil(2000, 4, 9) + end + + it "parses a date given MM/DD/YY" do + Date.strptime("04/06/00", "%x").should == Date.civil(2000, 4, 6) + Date.strptime("04/06/00", "%m/%d/%y").should == Date.civil(2000, 4, 6) + end + + it "parses a date given in full notation" do + Date.strptime("Sun Apr 9 00:00:00 +00:00 2000", "%+").should == Date.civil(2000, 4, 9) + Date.strptime("Sun Apr 9 00:00:00 +00:00 2000", "%a %b %e %H:%M:%S %Z %Y").should == Date.civil(2000, 4, 9) + end + +end + +describe "Date.strptime" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/succ_spec.rb b/spec/ruby/library/date/succ_spec.rb new file mode 100644 index 0000000000..c4a902aa63 --- /dev/null +++ b/spec/ruby/library/date/succ_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#succ" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/sunday_spec.rb b/spec/ruby/library/date/sunday_spec.rb new file mode 100644 index 0000000000..c3a817fa86 --- /dev/null +++ b/spec/ruby/library/date/sunday_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#sunday?" do + it "should be sunday" do + Date.new(2000, 1, 2).sunday?.should be_true + end +end diff --git a/spec/ruby/library/date/thursday_spec.rb b/spec/ruby/library/date/thursday_spec.rb new file mode 100644 index 0000000000..74b5f40365 --- /dev/null +++ b/spec/ruby/library/date/thursday_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#thursday?" do + it "should be thursday" do + Date.new(2000, 1, 6).thursday?.should be_true + end +end diff --git a/spec/ruby/library/date/time/to_date_spec.rb b/spec/ruby/library/date/time/to_date_spec.rb new file mode 100644 index 0000000000..f9132da289 --- /dev/null +++ b/spec/ruby/library/date/time/to_date_spec.rb @@ -0,0 +1,42 @@ + +require_relative '../../../spec_helper' +require 'time' + +describe "Time#to_date" do + it "yields accurate julian date for ambiguous pre-Gregorian reform value" do + Time.utc(1582, 10, 4).to_date.jd.should == Date::ITALY - 11 # 2299150j + end + + it "yields accurate julian date for Julian-Gregorian gap value" do + Time.utc(1582, 10, 14).to_date.jd.should == Date::ITALY - 1 # 2299160j + end + + it "yields accurate julian date for post-Gregorian reform value" do + Time.utc(1582, 10, 15).to_date.jd.should == Date::ITALY # 2299161j + end + + it "yields same julian day regardless of UTC time value" do + Time.utc(1582, 10, 15, 00, 00, 00).to_date.jd.should == Date::ITALY + Time.utc(1582, 10, 15, 23, 59, 59).to_date.jd.should == Date::ITALY + end + + it "yields same julian day regardless of local time or zone" do + + with_timezone("Pacific/Pago_Pago", -11) do + Time.local(1582, 10, 15, 00, 00, 00).to_date.jd.should == Date::ITALY + Time.local(1582, 10, 15, 23, 59, 59).to_date.jd.should == Date::ITALY + end + + with_timezone("Asia/Kamchatka", +12) do + Time.local(1582, 10, 15, 00, 00, 00).to_date.jd.should == Date::ITALY + Time.local(1582, 10, 15, 23, 59, 59).to_date.jd.should == Date::ITALY + end + + end + + it "yields date with default Calendar reform day" do + Time.utc(1582, 10, 4).to_date.start.should == Date::ITALY + Time.utc(1582, 10, 14).to_date.start.should == Date::ITALY + Time.utc(1582, 10, 15).to_date.start.should == Date::ITALY + end +end diff --git a/spec/ruby/library/date/time_to_day_fraction_spec.rb b/spec/ruby/library/date/time_to_day_fraction_spec.rb new file mode 100644 index 0000000000..e59980e036 --- /dev/null +++ b/spec/ruby/library/date/time_to_day_fraction_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.time_to_day_fraction" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/to_s_spec.rb b/spec/ruby/library/date/to_s_spec.rb new file mode 100644 index 0000000000..fe7cb19a46 --- /dev/null +++ b/spec/ruby/library/date/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#to_s" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/today_spec.rb b/spec/ruby/library/date/today_spec.rb new file mode 100644 index 0000000000..7c6ebc9cb4 --- /dev/null +++ b/spec/ruby/library/date/today_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.today" do + it "returns a Date object" do + Date.today.should be_kind_of Date + end + + it "sets Date object to the current date" do + today = Date.today + now = Time.now + (now - today.to_time).should be_close(0.0, 24 * 60 * 60) + end +end diff --git a/spec/ruby/library/date/tuesday_spec.rb b/spec/ruby/library/date/tuesday_spec.rb new file mode 100644 index 0000000000..052837b54e --- /dev/null +++ b/spec/ruby/library/date/tuesday_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#tuesday?" do + it "should be tuesday" do + Date.new(2000, 1, 4).tuesday?.should be_true + end +end diff --git a/spec/ruby/library/date/upto_spec.rb b/spec/ruby/library/date/upto_spec.rb new file mode 100644 index 0000000000..8745be85b3 --- /dev/null +++ b/spec/ruby/library/date/upto_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#upto" do + it "returns future dates for the default step value" do + ds = Date.civil(2008, 10, 11) + de = Date.civil(2008, 9, 29) + count = 0 + de.upto(ds) do |d| + d.should <= ds + d.should >= de + count += 1 + end + count.should == 13 + end +end diff --git a/spec/ruby/library/date/valid_civil_spec.rb b/spec/ruby/library/date/valid_civil_spec.rb new file mode 100644 index 0000000000..00f2c57205 --- /dev/null +++ b/spec/ruby/library/date/valid_civil_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'shared/valid_civil' +require 'date' + +describe "Date#valid_civil?" do + + it_behaves_like :date_valid_civil?, :valid_civil? + +end diff --git a/spec/ruby/library/date/valid_commercial_spec.rb b/spec/ruby/library/date/valid_commercial_spec.rb new file mode 100644 index 0000000000..7e96782b6b --- /dev/null +++ b/spec/ruby/library/date/valid_commercial_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'shared/valid_commercial' +require 'date' + +describe "Date#valid_commercial?" do + + it_behaves_like :date_valid_commercial?, :valid_commercial? +end diff --git a/spec/ruby/library/date/valid_date_spec.rb b/spec/ruby/library/date/valid_date_spec.rb new file mode 100644 index 0000000000..f12a71d966 --- /dev/null +++ b/spec/ruby/library/date/valid_date_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/valid_civil' +require 'date' + +describe "Date#valid_date?" do + it_behaves_like :date_valid_civil?, :valid_date? +end diff --git a/spec/ruby/library/date/valid_jd_spec.rb b/spec/ruby/library/date/valid_jd_spec.rb new file mode 100644 index 0000000000..aecaaabcf4 --- /dev/null +++ b/spec/ruby/library/date/valid_jd_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'shared/valid_jd' +require 'date' + +describe "Date.valid_jd?" do + + it_behaves_like :date_valid_jd?, :valid_jd? + +end diff --git a/spec/ruby/library/date/valid_ordinal_spec.rb b/spec/ruby/library/date/valid_ordinal_spec.rb new file mode 100644 index 0000000000..58d548c704 --- /dev/null +++ b/spec/ruby/library/date/valid_ordinal_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'shared/valid_ordinal' +require 'date' + +describe "Date.valid_ordinal?" do + + it_behaves_like :date_valid_ordinal?, :valid_ordinal? + +end diff --git a/spec/ruby/library/date/valid_time_spec.rb b/spec/ruby/library/date/valid_time_spec.rb new file mode 100644 index 0000000000..87c239bedb --- /dev/null +++ b/spec/ruby/library/date/valid_time_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.valid_time?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/date/wday_spec.rb b/spec/ruby/library/date/wday_spec.rb new file mode 100644 index 0000000000..303905ed35 --- /dev/null +++ b/spec/ruby/library/date/wday_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#wday" do + it "returns the week day as a number starting with Sunday as 0" do + w = Date.new(2000, 1, 1).wday + w.should == 6 + end +end diff --git a/spec/ruby/library/date/wednesday_spec.rb b/spec/ruby/library/date/wednesday_spec.rb new file mode 100644 index 0000000000..e80ec23dd2 --- /dev/null +++ b/spec/ruby/library/date/wednesday_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#wednesday?" do + it "should be wednesday" do + Date.new(2000, 1, 5).wednesday?.should be_true + end +end diff --git a/spec/ruby/library/date/yday_spec.rb b/spec/ruby/library/date/yday_spec.rb new file mode 100644 index 0000000000..7dd42e52a5 --- /dev/null +++ b/spec/ruby/library/date/yday_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/time/yday' +require 'date' + +describe "Date#yday" do + it_behaves_like :time_yday, -> year, month, day { Date.new(year, month, day).yday } +end diff --git a/spec/ruby/library/date/year_spec.rb b/spec/ruby/library/date/year_spec.rb new file mode 100644 index 0000000000..90d14e5a39 --- /dev/null +++ b/spec/ruby/library/date/year_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date#year" do + it "returns the year" do + y = Date.new(2000, 7, 1).year + y.should == 2000 + end +end diff --git a/spec/ruby/library/date/zone_to_diff_spec.rb b/spec/ruby/library/date/zone_to_diff_spec.rb new file mode 100644 index 0000000000..354daaaee4 --- /dev/null +++ b/spec/ruby/library/date/zone_to_diff_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "Date.zone_to_diff" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/_strptime_spec.rb b/spec/ruby/library/datetime/_strptime_spec.rb new file mode 100644 index 0000000000..abec26ff9f --- /dev/null +++ b/spec/ruby/library/datetime/_strptime_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime._strptime" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/add_spec.rb b/spec/ruby/library/datetime/add_spec.rb new file mode 100644 index 0000000000..20288e2105 --- /dev/null +++ b/spec/ruby/library/datetime/add_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#+" do + it "is able to add sub-millisecond precision values" do + datetime = DateTime.new(2017) + (datetime + 0.00001001).to_time.usec.should == 864864 + end +end diff --git a/spec/ruby/library/datetime/civil_spec.rb b/spec/ruby/library/datetime/civil_spec.rb new file mode 100644 index 0000000000..fb6f67f16d --- /dev/null +++ b/spec/ruby/library/datetime/civil_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.civil" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/commercial_spec.rb b/spec/ruby/library/datetime/commercial_spec.rb new file mode 100644 index 0000000000..ad97b2a80e --- /dev/null +++ b/spec/ruby/library/datetime/commercial_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.commercial" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/deconstruct_keys_spec.rb b/spec/ruby/library/datetime/deconstruct_keys_spec.rb new file mode 100644 index 0000000000..154c024a23 --- /dev/null +++ b/spec/ruby/library/datetime/deconstruct_keys_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' +require 'date' +date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0' + +describe "DateTime#deconstruct_keys" do + it "returns whole hash for nil as an argument" do + d = DateTime.new(2022, 10, 5, 13, 30) + res = { year: 2022, month: 10, day: 5, yday: 278, wday: 3, hour: 13, + min: 30, sec: 0, sec_fraction: (0/1), zone: "+00:00" } + d.deconstruct_keys(nil).should == res + end + + it "returns only specified keys" do + d = DateTime.new(2022, 10, 5, 13, 39) + d.deconstruct_keys([:zone, :hour]).should == { zone: "+00:00", hour: 13 } + end + + it "requires one argument" do + -> { + DateTime.new(2022, 10, 5, 13, 30).deconstruct_keys + }.should raise_error(ArgumentError) + end + + it "it raises error when argument is neither nil nor array" do + d = DateTime.new(2022, 10, 5, 13, 30) + + -> { d.deconstruct_keys(1) }.should raise_error(TypeError, "wrong argument type Integer (expected Array or nil)") + -> { d.deconstruct_keys("asd") }.should raise_error(TypeError, "wrong argument type String (expected Array or nil)") + -> { d.deconstruct_keys(:x) }.should raise_error(TypeError, "wrong argument type Symbol (expected Array or nil)") + -> { d.deconstruct_keys({}) }.should raise_error(TypeError, "wrong argument type Hash (expected Array or nil)") + end + + it "returns {} when passed []" do + DateTime.new(2022, 10, 5, 13, 30).deconstruct_keys([]).should == {} + end + + it "ignores non-Symbol keys" do + DateTime.new(2022, 10, 5, 13, 30).deconstruct_keys(['year', []]).should == {} + end + + it "ignores not existing Symbol keys" do + DateTime.new(2022, 10, 5, 13, 30).deconstruct_keys([:year, :a]).should == { year: 2022 } + end +end diff --git a/spec/ruby/library/datetime/hour_spec.rb b/spec/ruby/library/datetime/hour_spec.rb new file mode 100644 index 0000000000..8efd5f92f0 --- /dev/null +++ b/spec/ruby/library/datetime/hour_spec.rb @@ -0,0 +1,47 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#hour" do + it "returns 0 if no argument is passed" do + DateTime.new.hour.should == 0 + end + + it "returns the hour given as argument" do + new_datetime(hour: 5).hour.should == 5 + end + + it "adds 24 to negative hours" do + new_datetime(hour: -10).hour.should == 14 + end + + it "raises an error for Rational" do + -> { new_datetime(hour: 1 + Rational(1,2)) }.should raise_error(ArgumentError) + end + + it "raises an error for Float" do + -> { new_datetime(hour: 1.5).hour }.should raise_error(ArgumentError) + end + + it "raises an error for Rational" do + -> { new_datetime(day: 1 + Rational(1,2)) }.should raise_error(ArgumentError) + end + + it "raises an error, when the hour is smaller than -24" do + -> { new_datetime(hour: -25) }.should raise_error(ArgumentError) + end + + it "raises an error, when the hour is larger than 24" do + -> { new_datetime(hour: 25) }.should raise_error(ArgumentError) + end + + it "raises an error for hour fractions smaller than -24" do + -> { new_datetime(hour: -24 - Rational(1,2)) }.should( + raise_error(ArgumentError)) + end + + it "adds 1 to day, when 24 hours given" do + d = new_datetime day: 1, hour: 24 + d.hour.should == 0 + d.day.should == 2 + end +end diff --git a/spec/ruby/library/datetime/httpdate_spec.rb b/spec/ruby/library/datetime/httpdate_spec.rb new file mode 100644 index 0000000000..68e45cbff0 --- /dev/null +++ b/spec/ruby/library/datetime/httpdate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.httpdate" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/iso8601_spec.rb b/spec/ruby/library/datetime/iso8601_spec.rb new file mode 100644 index 0000000000..457881277a --- /dev/null +++ b/spec/ruby/library/datetime/iso8601_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.iso8601" do + it "needs to be reviewed for spec completeness" +end + +describe "DateTime#iso8601" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/jd_spec.rb b/spec/ruby/library/datetime/jd_spec.rb new file mode 100644 index 0000000000..1e783f5af4 --- /dev/null +++ b/spec/ruby/library/datetime/jd_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.jd" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/jisx0301_spec.rb b/spec/ruby/library/datetime/jisx0301_spec.rb new file mode 100644 index 0000000000..ab26aa2d73 --- /dev/null +++ b/spec/ruby/library/datetime/jisx0301_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.jisx0301" do + it "needs to be reviewed for spec completeness" +end + +describe "DateTime#jisx0301" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/min_spec.rb b/spec/ruby/library/datetime/min_spec.rb new file mode 100644 index 0000000000..a1eaa214cb --- /dev/null +++ b/spec/ruby/library/datetime/min_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/min' + +describe "DateTime.min" do + it_behaves_like :datetime_min, :min +end diff --git a/spec/ruby/library/datetime/minute_spec.rb b/spec/ruby/library/datetime/minute_spec.rb new file mode 100644 index 0000000000..acdfeda345 --- /dev/null +++ b/spec/ruby/library/datetime/minute_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/min' + +describe "DateTime.minute" do + it_behaves_like :datetime_min, :minute +end diff --git a/spec/ruby/library/datetime/new_offset_spec.rb b/spec/ruby/library/datetime/new_offset_spec.rb new file mode 100644 index 0000000000..bc0988f32d --- /dev/null +++ b/spec/ruby/library/datetime/new_offset_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#new_offset" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/new_spec.rb b/spec/ruby/library/datetime/new_spec.rb new file mode 100644 index 0000000000..6a4dced384 --- /dev/null +++ b/spec/ruby/library/datetime/new_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.new" do + it "sets all values to default if passed no arguments" do + d = DateTime.new + d.year.should == -4712 + d.month.should == 1 + d.day.should == 1 + d.hour.should == 0 + d.min.should == 0 + d.sec.should == 0 + d.sec_fraction.should == 0 + d.offset.should == 0 + end + + it "takes the first argument as year" do + DateTime.new(2011).year.should == 2011 + end + + it "takes the second argument as month" do + DateTime.new(2011, 2).month.should == 2 + end + + it "takes the third argument as day" do + DateTime.new(2011, 2, 3).day.should == 3 + end + + it "takes the forth argument as hour" do + DateTime.new(2011, 2, 3, 4).hour.should == 4 + end + + it "takes the fifth argument as minute" do + DateTime.new(1, 2, 3, 4, 5).min.should == 5 + end + + it "takes the sixth argument as second" do + DateTime.new(1, 2, 3, 4, 5, 6).sec.should == 6 + end + + it "takes the seventh argument as an offset" do + DateTime.new(1, 2, 3, 4, 5, 6, 0.7).offset.should == 0.7 + end + + it "takes the eighth argument as the date of calendar reform" do + DateTime.new(1, 2, 3, 4, 5, 6, 0.7, Date::ITALY).start().should == Date::ITALY + end + + it "raises an error on invalid arguments" do + -> { new_datetime(minute: 999) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/datetime/now_spec.rb b/spec/ruby/library/datetime/now_spec.rb new file mode 100644 index 0000000000..9f22153c15 --- /dev/null +++ b/spec/ruby/library/datetime/now_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.now" do + it "creates an instance of DateTime" do + DateTime.now.should be_an_instance_of(DateTime) + end + + it "sets the current date" do + (DateTime.now - Date.today).to_f.should be_close(0.0, TIME_TOLERANCE) + end + + it "sets the current time" do + dt = DateTime.now + now = Time.now + (dt.to_time - now).should be_close(0.0, TIME_TOLERANCE) + end + + it "grabs the local timezone" do + with_timezone("PDT", -8) do + dt = DateTime.now + dt.zone.should == "-08:00" + end + end +end diff --git a/spec/ruby/library/datetime/offset_spec.rb b/spec/ruby/library/datetime/offset_spec.rb new file mode 100644 index 0000000000..e25e7a0f35 --- /dev/null +++ b/spec/ruby/library/datetime/offset_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#offset" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/ordinal_spec.rb b/spec/ruby/library/datetime/ordinal_spec.rb new file mode 100644 index 0000000000..64b154ee9b --- /dev/null +++ b/spec/ruby/library/datetime/ordinal_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.ordinal" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/parse_spec.rb b/spec/ruby/library/datetime/parse_spec.rb new file mode 100644 index 0000000000..e9bf4e2ed1 --- /dev/null +++ b/spec/ruby/library/datetime/parse_spec.rb @@ -0,0 +1,127 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.parse" do + + it "parses a day name into a DateTime object" do + d = DateTime.parse("friday") + d.should == DateTime.commercial(d.cwyear, d.cweek, 5) + end + + it "parses a month name into a DateTime object" do + d = DateTime.parse("october") + d.should == DateTime.civil(Date.today.year, 10) + end + + it "parses a month day into a DateTime object" do + d = DateTime.parse("5th") + d.should == DateTime.civil(Date.today.year, Date.today.month, 5) + end + + # Specs using numbers + it "throws an argument error for a single digit" do + ->{ DateTime.parse("1") }.should raise_error(ArgumentError) + end + + it "parses DD as month day number" do + d = DateTime.parse("10") + d.should == DateTime.civil(Date.today.year, Date.today.month, 10) + end + + it "parses DDD as year day number" do + d = DateTime.parse("100") + if DateTime.gregorian_leap?(Date.today.year) + d.should == DateTime.civil(Date.today.year, 4, 9) + else + d.should == DateTime.civil(Date.today.year, 4, 10) + end + end + + it "parses MMDD as month and day" do + d = DateTime.parse("1108") + d.should == DateTime.civil(Date.today.year, 11, 8) + end + + it "parses YYYYMMDD as year, month and day" do + d = DateTime.parse("20121108") + d.should == DateTime.civil(2012, 11, 8) + end + + describe "YYYY-MM-DDTHH:MM:SS format" do + it "parses YYYY-MM-DDTHH:MM:SS into a DateTime object" do + d = DateTime.parse("2012-11-08T15:43:59") + d.should == DateTime.civil(2012, 11, 8, 15, 43, 59) + end + + it "throws an argument error for invalid month values" do + ->{DateTime.parse("2012-13-08T15:43:59")}.should raise_error(ArgumentError) + end + + it "throws an argument error for invalid day values" do + ->{DateTime.parse("2012-12-32T15:43:59")}.should raise_error(ArgumentError) + end + + it "throws an argument error for invalid hour values" do + ->{DateTime.parse("2012-12-31T25:43:59")}.should raise_error(ArgumentError) + end + + it "throws an argument error for invalid minute values" do + ->{DateTime.parse("2012-12-31T25:43:59")}.should raise_error(ArgumentError) + end + + it "throws an argument error for invalid second values" do + ->{DateTime.parse("2012-11-08T15:43:61")}.should raise_error(ArgumentError) + end + + end + + it "parses YYDDD as year and day number in 1969--2068" do + d = DateTime.parse("10100") + d.should == DateTime.civil(2010, 4, 10) + end + + it "parses YYMMDD as year, month and day in 1969--2068" do + d = DateTime.parse("201023") + d.should == DateTime.civil(2020, 10, 23) + end + + it "parses YYYYDDD as year and day number" do + d = DateTime.parse("1910100") + d.should == DateTime.civil(1910, 4, 10) + end + + it "parses YYYYMMDD as year, month and day number" do + d = DateTime.parse("19101101") + d.should == DateTime.civil(1910, 11, 1) + end +end + +describe "DateTime.parse(.)" do + it "parses YYYY.MM.DD into a DateTime object" do + d = DateTime.parse("2007.10.01") + d.year.should == 2007 + d.month.should == 10 + d.day.should == 1 + end + + it "parses DD.MM.YYYY into a DateTime object" do + d = DateTime.parse("10.01.2007") + d.year.should == 2007 + d.month.should == 1 + d.day.should == 10 + end + + it "parses YY.MM.DD into a DateTime object using the year 20YY" do + d = DateTime.parse("10.01.07") + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end + + it "parses YY.MM.DD using the year digits as 20YY when given true as additional argument" do + d = DateTime.parse("10.01.07", true) + d.year.should == 2010 + d.month.should == 1 + d.day.should == 7 + end +end diff --git a/spec/ruby/library/datetime/rfc2822_spec.rb b/spec/ruby/library/datetime/rfc2822_spec.rb new file mode 100644 index 0000000000..83f7fa8d5b --- /dev/null +++ b/spec/ruby/library/datetime/rfc2822_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.rfc2822" do + it "needs to be reviewed for spec completeness" + + it "raises DateError if passed nil" do + -> { DateTime.rfc2822(nil) }.should raise_error(Date::Error, "invalid date") + end +end diff --git a/spec/ruby/library/datetime/rfc3339_spec.rb b/spec/ruby/library/datetime/rfc3339_spec.rb new file mode 100644 index 0000000000..f870a5f63b --- /dev/null +++ b/spec/ruby/library/datetime/rfc3339_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.rfc3339" do + it "needs to be reviewed for spec completeness" +end + +describe "DateTime#rfc3339" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/rfc822_spec.rb b/spec/ruby/library/datetime/rfc822_spec.rb new file mode 100644 index 0000000000..0cd0aacc19 --- /dev/null +++ b/spec/ruby/library/datetime/rfc822_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.rfc822" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/sec_fraction_spec.rb b/spec/ruby/library/datetime/sec_fraction_spec.rb new file mode 100644 index 0000000000..40383a8ca4 --- /dev/null +++ b/spec/ruby/library/datetime/sec_fraction_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#sec_fraction" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/sec_spec.rb b/spec/ruby/library/datetime/sec_spec.rb new file mode 100644 index 0000000000..f681283c8e --- /dev/null +++ b/spec/ruby/library/datetime/sec_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/sec' + +describe "DateTime.sec" do + it_behaves_like :datetime_sec, :sec +end diff --git a/spec/ruby/library/datetime/second_fraction_spec.rb b/spec/ruby/library/datetime/second_fraction_spec.rb new file mode 100644 index 0000000000..d5393149ba --- /dev/null +++ b/spec/ruby/library/datetime/second_fraction_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#second_fraction" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/second_spec.rb b/spec/ruby/library/datetime/second_spec.rb new file mode 100644 index 0000000000..545c3f9109 --- /dev/null +++ b/spec/ruby/library/datetime/second_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/sec' + +describe "DateTime#second" do + it_behaves_like :datetime_sec, :second +end diff --git a/spec/ruby/library/datetime/shared/min.rb b/spec/ruby/library/datetime/shared/min.rb new file mode 100644 index 0000000000..a35b839281 --- /dev/null +++ b/spec/ruby/library/datetime/shared/min.rb @@ -0,0 +1,40 @@ +require 'date' + +describe :datetime_min, shared: true do + it "returns 0 if no argument is passed" do + DateTime.new.send(@method).should == 0 + end + + it "returns the minute passed as argument" do + new_datetime(minute: 5).send(@method).should == 5 + end + + it "adds 60 to negative minutes" do + new_datetime(minute: -20).send(@method).should == 40 + end + + it "raises an error for Rational" do + -> { new_datetime minute: 5 + Rational(1,2) }.should raise_error(ArgumentError) + end + + it "raises an error for Float" do + -> { new_datetime minute: 5.5 }.should raise_error(ArgumentError) + end + + it "raises an error for Rational" do + -> { new_datetime(hour: 2 + Rational(1,2)) }.should raise_error(ArgumentError) + end + + it "raises an error, when the minute is smaller than -60" do + -> { new_datetime(minute: -61) }.should raise_error(ArgumentError) + end + + it "raises an error, when the minute is greater or equal than 60" do + -> { new_datetime(minute: 60) }.should raise_error(ArgumentError) + end + + it "raises an error for minute fractions smaller than -60" do + -> { new_datetime(minute: -60 - Rational(1,2))}.should( + raise_error(ArgumentError)) + end +end diff --git a/spec/ruby/library/datetime/shared/sec.rb b/spec/ruby/library/datetime/shared/sec.rb new file mode 100644 index 0000000000..60009213aa --- /dev/null +++ b/spec/ruby/library/datetime/shared/sec.rb @@ -0,0 +1,45 @@ +require 'date' + +describe :datetime_sec, shared: true do + it "returns 0 seconds if passed no arguments" do + d = DateTime.new + d.send(@method).should == 0 + end + + it "returns the seconds passed in the arguments" do + new_datetime(second: 5).send(@method).should == 5 + end + + it "adds 60 to negative values" do + new_datetime(second: -20).send(@method).should == 40 + end + + it "returns the absolute value of a Rational" do + new_datetime(second: 5 + Rational(1,2)).send(@method).should == 5 + end + + it "returns the absolute value of a float" do + new_datetime(second: 5.5).send(@method).should == 5 + end + + it "raises an error when minute is given as a rational" do + -> { new_datetime(minute: 5 + Rational(1,2)) }.should raise_error(ArgumentError) + end + + it "raises an error, when the second is smaller than -60" do + -> { new_datetime(second: -61) }.should raise_error(ArgumentError) + end + + it "raises an error, when the second is greater or equal than 60" do + -> { new_datetime(second: 60) }.should raise_error(ArgumentError) + end + + it "raises an error for second fractions smaller than -60" do + -> { new_datetime(second: -60 - Rational(1,2))}.should( + raise_error(ArgumentError)) + end + + it "takes a second fraction near 60" do + new_datetime(second: 59 + Rational(1,2)).send(@method).should == 59 + end +end diff --git a/spec/ruby/library/datetime/strftime_spec.rb b/spec/ruby/library/datetime/strftime_spec.rb new file mode 100644 index 0000000000..a07cc9c1aa --- /dev/null +++ b/spec/ruby/library/datetime/strftime_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' +require 'date' +require_relative '../../shared/time/strftime_for_date' +require_relative '../../shared/time/strftime_for_time' +date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0' + +describe "DateTime#strftime" do + before :all do + @new_date = -> y, m, d { DateTime.civil(y,m,d) } + @new_time = -> *args { DateTime.civil(*args) } + @new_time_in_zone = -> zone, offset, *args { + y, m, d, h, min, s = args + DateTime.new(y, m||1, d||1, h||0, min||0, s||0, Rational(offset, 24)) + } + @new_time_with_offset = -> y, m, d, h, min, s, offset { + DateTime.new(y,m,d,h,min,s, Rational(offset, 86_400)) + } + + @time = DateTime.civil(2001, 2, 3, 4, 5, 6) + end + + it_behaves_like :strftime_date, :strftime + it_behaves_like :strftime_time, :strftime + + # Differences with Time + it "should be able to print the datetime with no argument" do + @time.strftime.should == "2001-02-03T04:05:06+00:00" + @time.strftime.should == @time.to_s + end + + # %Z is %:z for Date/DateTime + it "should be able to show the timezone with a : separator" do + @time.strftime("%Z").should == "+00:00" + end + + it "should be able to show the commercial week" do + @time.strftime("%v").should == " 3-FEB-2001" + @time.strftime("%v").should != @time.strftime('%e-%b-%Y') + end + + # additional conversion specifiers only in Date/DateTime + it 'shows the number of milliseconds since epoch' do + DateTime.new(1970, 1, 1, 0, 0, 0).strftime("%Q").should == "0" + @time.strftime("%Q").should == "981173106000" + DateTime.civil(2001, 2, 3, 4, 5, Rational(6123, 1000)).strftime("%Q").should == "981173106123" + end + + it "should be able to show a full notation" do + @time.strftime("%+").should == "Sat Feb 3 04:05:06 +00:00 2001" + @time.strftime("%+").should == @time.strftime('%a %b %e %H:%M:%S %Z %Y') + end +end diff --git a/spec/ruby/library/datetime/strptime_spec.rb b/spec/ruby/library/datetime/strptime_spec.rb new file mode 100644 index 0000000000..d1e83550e4 --- /dev/null +++ b/spec/ruby/library/datetime/strptime_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.strptime" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/subtract_spec.rb b/spec/ruby/library/datetime/subtract_spec.rb new file mode 100644 index 0000000000..ba01f4eff6 --- /dev/null +++ b/spec/ruby/library/datetime/subtract_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#-" do + it "is able to subtract sub-millisecond precision values" do + date = DateTime.new(2017) + diff = Rational(123456789, 24*60*60*1000*1000) + ((date + diff) - date).should == diff + (date - (date + diff)).should == -diff + (date - (date - diff)).should == diff + ((date - diff) - date).should == -diff + end + + it "correctly calculates sub-millisecond time differences" do #5493 + dt1 = DateTime.new(2018, 1, 1, 0, 0, 30) + dt2 = DateTime.new(2018, 1, 1, 0, 1, 29.000001) + ((dt2 - dt1) * 24 * 60 * 60).should == 59.000001 + end +end diff --git a/spec/ruby/library/datetime/time/to_datetime_spec.rb b/spec/ruby/library/datetime/time/to_datetime_spec.rb new file mode 100644 index 0000000000..5589725238 --- /dev/null +++ b/spec/ruby/library/datetime/time/to_datetime_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../../spec_helper' +require 'time' +require 'date' +date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0' + +describe "Time#to_datetime" do + it "returns a DateTime representing the same instant" do + time = Time.utc(2012, 12, 31, 23, 58, 59) + datetime = time.to_datetime + datetime.year.should == 2012 + datetime.month.should == 12 + datetime.day.should == 31 + datetime.hour.should == 23 + datetime.min.should == 58 + datetime.sec.should == 59 + end + + it "returns a DateTime representing the same instant before Gregorian" do + time = Time.utc(1582, 10, 14, 23, 58, 59) + datetime = time.to_datetime + datetime.year.should == 1582 + datetime.month.should == 10 + datetime.day.should == 4 + datetime.hour.should == 23 + datetime.min.should == 58 + datetime.sec.should == 59 + end + + it "roundtrips" do + time = Time.utc(3, 12, 31, 23, 58, 59) + datetime = time.to_datetime + datetime.to_time.utc.should == time + end + + it "yields a DateTime with the default Calendar reform day" do + Time.utc(1582, 10, 4, 1, 2, 3).to_datetime.start.should == Date::ITALY + Time.utc(1582, 10, 14, 1, 2, 3).to_datetime.start.should == Date::ITALY + Time.utc(1582, 10, 15, 1, 2, 3).to_datetime.start.should == Date::ITALY + end +end diff --git a/spec/ruby/library/datetime/to_date_spec.rb b/spec/ruby/library/datetime/to_date_spec.rb new file mode 100644 index 0000000000..48c05e7fed --- /dev/null +++ b/spec/ruby/library/datetime/to_date_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#to_date" do + it "returns an instance of Date" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + dt.to_date.should be_kind_of(Date) + end + + it "maintains the same year" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + dt.to_date.year.should == dt.year + end + + it "maintains the same month" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + dt.to_date.mon.should == dt.mon + end + + it "maintains the same day" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + dt.to_date.day.should == dt.day + end + + it "maintains the same mday" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + dt.to_date.mday.should == dt.mday + end + + it "maintains the same julian day regardless of local time or zone" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + + with_timezone("Pacific/Pago_Pago", -11) do + dt.to_date.jd.should == dt.jd + end + end +end diff --git a/spec/ruby/library/datetime/to_datetime_spec.rb b/spec/ruby/library/datetime/to_datetime_spec.rb new file mode 100644 index 0000000000..95ee29268f --- /dev/null +++ b/spec/ruby/library/datetime/to_datetime_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#to_datetime" do + it "returns itself" do + dt = DateTime.new(2012, 12, 24, 12, 23, 00, '+05:00') + dt.to_datetime.should == dt + end +end diff --git a/spec/ruby/library/datetime/to_s_spec.rb b/spec/ruby/library/datetime/to_s_spec.rb new file mode 100644 index 0000000000..175fb807f4 --- /dev/null +++ b/spec/ruby/library/datetime/to_s_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#to_s" do + it "returns a new String object" do + dt = DateTime.new(2012, 12, 24, 1, 2, 3, "+03:00") + dt.to_s.should be_kind_of(String) + end + + it "maintains timezone regardless of local time" do + dt = DateTime.new(2012, 12, 24, 1, 2, 3, "+03:00") + + with_timezone("Pacific/Pago_Pago", -11) do + dt.to_s.should == "2012-12-24T01:02:03+03:00" + end + end +end diff --git a/spec/ruby/library/datetime/to_time_spec.rb b/spec/ruby/library/datetime/to_time_spec.rb new file mode 100644 index 0000000000..58bb363653 --- /dev/null +++ b/spec/ruby/library/datetime/to_time_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require 'date' +date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0' + +describe "DateTime#to_time" do + it "yields a new Time object" do + DateTime.now.to_time.should be_kind_of(Time) + end + + it "returns a Time representing the same instant" do + datetime = DateTime.civil(2012, 12, 31, 23, 58, 59) + time = datetime.to_time.utc + + time.year.should == 2012 + time.month.should == 12 + time.day.should == 31 + time.hour.should == 23 + time.min.should == 58 + time.sec.should == 59 + end + + it "returns a Time representing the same instant before Gregorian" do + datetime = DateTime.civil(1582, 10, 4, 23, 58, 59) + time = datetime.to_time.utc + time.year.should == 1582 + time.month.should == 10 + time.day.should == 14 + time.hour.should == 23 + time.min.should == 58 + time.sec.should == 59 + end + + it "preserves the same time regardless of local time or zone" do + date = DateTime.new(2012, 12, 24, 12, 23, 00, '+03:00') + + with_timezone("Pacific/Pago_Pago", -11) do + time = date.to_time + + time.utc_offset.should == 3 * 3600 + time.year.should == date.year + time.mon.should == date.mon + time.day.should == date.day + time.hour.should == date.hour + time.min.should == date.min + time.sec.should == date.sec + end + end +end diff --git a/spec/ruby/library/datetime/xmlschema_spec.rb b/spec/ruby/library/datetime/xmlschema_spec.rb new file mode 100644 index 0000000000..42832631ed --- /dev/null +++ b/spec/ruby/library/datetime/xmlschema_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime.xmlschema" do + it "needs to be reviewed for spec completeness" +end + +describe "DateTime#xmlschema" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/datetime/yday_spec.rb b/spec/ruby/library/datetime/yday_spec.rb new file mode 100644 index 0000000000..08a72c6480 --- /dev/null +++ b/spec/ruby/library/datetime/yday_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/time/yday' +require 'date' + +describe "DateTime#yday" do + it_behaves_like :time_yday, -> year, month, day { DateTime.new(year, month, day).yday } +end diff --git a/spec/ruby/library/datetime/zone_spec.rb b/spec/ruby/library/datetime/zone_spec.rb new file mode 100644 index 0000000000..b2c10b4b3b --- /dev/null +++ b/spec/ruby/library/datetime/zone_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'date' + +describe "DateTime#zone" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb b/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb new file mode 100644 index 0000000000..16bf8d734c --- /dev/null +++ b/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "DelegateClass.instance_method" do + before :all do + @klass = DelegateSpecs::DelegateClass + @obj = @klass.new(DelegateSpecs::Simple.new) + end + + it "returns a method object for public instance methods of the delegated class" do + m = @klass.instance_method(:pub) + m.should be_an_instance_of(UnboundMethod) + m.bind(@obj).call.should == :foo + end + + it "returns a method object for protected instance methods of the delegated class" do + m = @klass.instance_method(:prot) + m.should be_an_instance_of(UnboundMethod) + m.bind(@obj).call.should == :protected + end + + it "raises a NameError for a private instance methods of the delegated class" do + -> { + @klass.instance_method(:priv) + }.should raise_error(NameError) + end + + it "returns a method object for public instance methods of the DelegateClass class" do + m = @klass.instance_method(:extra) + m.should be_an_instance_of(UnboundMethod) + m.bind(@obj).call.should == :cheese + end + + it "returns a method object for protected instance methods of the DelegateClass class" do + m = @klass.instance_method(:extra_protected) + m.should be_an_instance_of(UnboundMethod) + m.bind(@obj).call.should == :baz + end + + it "returns a method object for private instance methods of the DelegateClass class" do + m = @klass.instance_method(:extra_private) + m.should be_an_instance_of(UnboundMethod) + m.bind(@obj).call.should == :bar + end + + it "raises a NameError for an invalid method name" do + -> { + @klass.instance_method(:invalid_and_silly_method_name) + }.should raise_error(NameError) + end + +end diff --git a/spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb new file mode 100644 index 0000000000..6012ff72de --- /dev/null +++ b/spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "DelegateClass.instance_methods" do + before :all do + @methods = DelegateSpecs::DelegateClass.instance_methods + end + + it "includes all public methods of the delegated class" do + @methods.should include :pub + end + + it "includes all protected methods of the delegated class" do + @methods.should include :prot + end + + it "includes instance methods of the DelegateClass class" do + @methods.should include :extra + @methods.should include :extra_protected + end + + it "does not include private methods" do + @methods.should_not include :priv + @methods.should_not include :extra_private + end +end diff --git a/spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb new file mode 100644 index 0000000000..06b2115cc5 --- /dev/null +++ b/spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "DelegateClass.private_instance_methods" do + before :all do + @methods = DelegateSpecs::DelegateClass.private_instance_methods + end + + it "does not include any instance methods of the delegated class" do + @methods.should_not include :pub + @methods.should_not include :prot + @methods.should_not include :priv # since these are not forwarded... + end + + it "includes private instance methods of the DelegateClass class" do + @methods.should include :extra_private + end + + it "does not include public or protected instance methods of the DelegateClass class" do + @methods.should_not include :extra + @methods.should_not include :extra_protected + end +end diff --git a/spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb new file mode 100644 index 0000000000..ac6659ec1e --- /dev/null +++ b/spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "DelegateClass.protected_instance_methods" do + before :all do + @methods = DelegateSpecs::DelegateClass.protected_instance_methods + end + + it "does not include public methods of the delegated class" do + @methods.should_not include :pub + end + + it "includes the protected methods of the delegated class" do + @methods.should include :prot + end + + it "includes protected instance methods of the DelegateClass class" do + @methods.should include :extra_protected + end + + it "does not include public instance methods of the DelegateClass class" do + @methods.should_not include :extra + end + + it "does not include private methods" do + @methods.should_not include :priv + @methods.should_not include :extra_private + end +end diff --git a/spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb b/spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb new file mode 100644 index 0000000000..6c0d9bcab1 --- /dev/null +++ b/spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "DelegateClass.public_instance_methods" do + before :all do + @methods = DelegateSpecs::DelegateClass.public_instance_methods + end + + it "includes all public methods of the delegated class" do + @methods.should include :pub + end + + it "does not include the protected methods of the delegated class" do + @methods.should_not include :prot + end + + it "includes public instance methods of the DelegateClass class" do + @methods.should include :extra + end + + it "does not include private methods" do + @methods.should_not include :priv + @methods.should_not include :extra_private + end +end diff --git a/spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb b/spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb new file mode 100644 index 0000000000..3975e5208b --- /dev/null +++ b/spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb @@ -0,0 +1,24 @@ +require_relative "../../../spec_helper" +require 'delegate' + +describe "DelegateClass#respond_to_missing?" do + it "is used for respond_to? behavior of late-bound delegated methods" do + # From jruby/jruby#3151: + # DelegateClass subtracts Delegate's public API from the target class's instance_methods + # to determine which methods to eagerly delegate. If respond_to_missing? shows up in + # instance_methods, it will get delegated and skip the delegate-aware implementation + # in Delegate. Any methods that must be delegated through method_missing, like methods + # defined after the DelegateClass is created, will fail to dispatch properly. + + cls = Class.new + dcls = DelegateClass(cls) + cdcls = Class.new(dcls) + cdcls_obj = cdcls.new(cls.new) + + cdcls_obj.respond_to?(:foo).should == false + + cls.class_eval { def foo; end } + + cdcls_obj.respond_to?(:foo).should == true + end +end diff --git a/spec/ruby/library/delegate/delegator/case_compare_spec.rb b/spec/ruby/library/delegate/delegator/case_compare_spec.rb new file mode 100644 index 0000000000..b62397cecf --- /dev/null +++ b/spec/ruby/library/delegate/delegator/case_compare_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#===" do + it "is delegated" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + base.should_receive(:===).with(42).and_return(:foo) + (delegator === 42).should == :foo + end +end diff --git a/spec/ruby/library/delegate/delegator/compare_spec.rb b/spec/ruby/library/delegate/delegator/compare_spec.rb new file mode 100644 index 0000000000..7dee5c2fb5 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/compare_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#<=>" do + it "is delegated" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + base.should_receive(:<=>).with(42).and_return(1) + (delegator <=> 42).should == 1 + end +end diff --git a/spec/ruby/library/delegate/delegator/complement_spec.rb b/spec/ruby/library/delegate/delegator/complement_spec.rb new file mode 100644 index 0000000000..10cb37c7ea --- /dev/null +++ b/spec/ruby/library/delegate/delegator/complement_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#~" do + it "is delegated" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + base.should_receive(:~).and_return(:foo) + (~delegator).should == :foo + end +end diff --git a/spec/ruby/library/delegate/delegator/eql_spec.rb b/spec/ruby/library/delegate/delegator/eql_spec.rb new file mode 100644 index 0000000000..34f56f44c9 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/eql_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#eql?" do + it "returns true when compared with same delegator" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + + delegator.eql?(delegator).should be_true + end + + it "returns true when compared with the inner object" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + + delegator.eql?(base).should be_true + end + + it "returns false when compared with the delegator with other object" do + base = mock('base') + other = mock('other') + delegator0 = DelegateSpecs::Delegator.new(base) + delegator1 = DelegateSpecs::Delegator.new(other) + + delegator0.eql?(delegator1).should be_false + end + + it "returns false when compared with the other object" do + base = mock('base') + other = mock('other') + delegator = DelegateSpecs::Delegator.new(base) + + delegator.eql?(other).should be_false + end +end diff --git a/spec/ruby/library/delegate/delegator/equal_spec.rb b/spec/ruby/library/delegate/delegator/equal_spec.rb new file mode 100644 index 0000000000..c8711c74b5 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/equal_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#equal?" do + it "returns true only when compared with the delegator" do + obj = mock('base') + delegator = DelegateSpecs::Delegator.new(obj) + obj.should_not_receive(:equal?) + delegator.equal?(obj).should be_false + delegator.equal?(nil).should be_false + delegator.equal?(delegator).should be_true + end +end diff --git a/spec/ruby/library/delegate/delegator/equal_value_spec.rb b/spec/ruby/library/delegate/delegator/equal_value_spec.rb new file mode 100644 index 0000000000..0c967d5f94 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/equal_value_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#==" do + before :all do + @base = mock('base') + @delegator = DelegateSpecs::Delegator.new(@base) + end + + it "is not delegated when passed self" do + @base.should_not_receive(:==) + (@delegator == @delegator).should be_true + end + + it "is delegated when passed the delegated object" do + @base.should_receive(:==).and_return(false) + (@delegator == @base).should be_false + end + + it "is delegated in general" do + @base.should_receive(:==).and_return(true) + (@delegator == 42).should be_true + end +end diff --git a/spec/ruby/library/delegate/delegator/frozen_spec.rb b/spec/ruby/library/delegate/delegator/frozen_spec.rb new file mode 100644 index 0000000000..b3145c54b1 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/frozen_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator when frozen" do + before :all do + @array = [42, :hello] + @delegate = DelegateSpecs::Delegator.new(@array) + @delegate.freeze + end + + it "is still readable" do + @delegate.should == [42, :hello] + @delegate.include?("bar").should be_false + end + + it "is frozen" do + @delegate.frozen?.should be_true + end + + it "is not writable" do + ->{ @delegate[0] += 2 }.should raise_error( RuntimeError ) + end + + it "creates a frozen clone" do + @delegate.clone.frozen?.should be_true + end + + it "creates an unfrozen dup" do + @delegate.dup.frozen?.should be_false + end + + it "causes mutative calls to raise RuntimeError" do + ->{ @delegate.__setobj__("hola!") }.should raise_error( RuntimeError ) + end + + it "returns false if only the delegated object is frozen" do + DelegateSpecs::Delegator.new([1,2,3].freeze).frozen?.should be_false + end +end diff --git a/spec/ruby/library/delegate/delegator/hash_spec.rb b/spec/ruby/library/delegate/delegator/hash_spec.rb new file mode 100644 index 0000000000..132cb91ccc --- /dev/null +++ b/spec/ruby/library/delegate/delegator/hash_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#hash" do + it "is delegated" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + base.should_receive(:hash).and_return(42) + delegator.hash.should == 42 + end +end diff --git a/spec/ruby/library/delegate/delegator/marshal_spec.rb b/spec/ruby/library/delegate/delegator/marshal_spec.rb new file mode 100644 index 0000000000..6c75c8f573 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/marshal_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'delegate' + +describe "SimpleDelegator" do + before :all do + @obj = "hello" + @delegate = SimpleDelegator.new(@obj) + end + + it "can be marshalled" do + m = Marshal.load(Marshal.dump(@delegate)) + m.class.should == SimpleDelegator + (m == @obj).should be_true + end + + it "can be marshalled with its instance variables intact" do + @delegate.instance_variable_set(:@foo, "bar") + m = Marshal.load(Marshal.dump(@delegate)) + m.instance_variable_get(:@foo).should == "bar" + end +end diff --git a/spec/ruby/library/delegate/delegator/method_spec.rb b/spec/ruby/library/delegate/delegator/method_spec.rb new file mode 100644 index 0000000000..81c8eea710 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/method_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#method" do + before :each do + @simple = DelegateSpecs::Simple.new + @delegate = DelegateSpecs::Delegator.new(@simple) + end + + it "returns a method object for public methods of the delegate object" do + m = @delegate.method(:pub) + m.should be_an_instance_of(Method) + m.call.should == :foo + end + + it "raises a NameError for protected methods of the delegate object" do + -> { + -> { + @delegate.method(:prot) + }.should complain(/delegator does not forward private method #prot/) + }.should raise_error(NameError) + end + + it "raises a NameError for a private methods of the delegate object" do + -> { + -> { + @delegate.method(:priv) + }.should complain(/delegator does not forward private method #priv/) + }.should raise_error(NameError) + end + + it "returns a method object for public methods of the Delegator class" do + m = @delegate.method(:extra) + m.should be_an_instance_of(Method) + m.call.should == :cheese + end + + it "returns a method object for protected methods of the Delegator class" do + m = @delegate.method(:extra_protected) + m.should be_an_instance_of(Method) + m.call.should == :baz + end + + it "returns a method object for private methods of the Delegator class" do + m = @delegate.method(:extra_private) + m.should be_an_instance_of(Method) + m.call.should == :bar + end + + it "raises a NameError for an invalid method name" do + -> { + @delegate.method(:invalid_and_silly_method_name) + }.should raise_error(NameError) + end + + it "returns a method that respond_to_missing?" do + m = @delegate.method(:pub_too) + m.should be_an_instance_of(Method) + m.call.should == :pub_too + end + + it "raises a NameError if method is no longer valid because object has changed" do + m = @delegate.method(:pub) + @delegate.__setobj__([1,2,3]) + -> { + m.call + }.should raise_error(NameError) + end +end diff --git a/spec/ruby/library/delegate/delegator/methods_spec.rb b/spec/ruby/library/delegate/delegator/methods_spec.rb new file mode 100644 index 0000000000..b9942bd230 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/methods_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#methods" do + before :all do + @simple = DelegateSpecs::Simple.new + class << @simple + def singleton_method + end + end + + @delegate = DelegateSpecs::Delegator.new(@simple) + @methods = @delegate.methods + end + + it "returns singleton methods when passed false" do + @delegate.methods(false).should include(:singleton_method) + end + + it "includes all public methods of the delegate object" do + @methods.should include :pub + end + + it "includes all protected methods of the delegate object" do + @methods.should include :prot + end + + it "includes instance methods of the Delegator class" do + @methods.should include :extra + @methods.should include :extra_protected + end + + it "does not include private methods" do + @methods.should_not include :priv + @methods.should_not include :extra_private + end +end diff --git a/spec/ruby/library/delegate/delegator/not_equal_spec.rb b/spec/ruby/library/delegate/delegator/not_equal_spec.rb new file mode 100644 index 0000000000..6f2df21715 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/not_equal_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#!=" do + before :all do + @base = mock('base') + @delegator = DelegateSpecs::Delegator.new(@base) + end + + it "is not delegated when passed self" do + @base.should_not_receive(:"!=") + (@delegator != @delegator).should be_false + end + + it "is delegated when passed the delegated object" do + @base.should_receive(:"!=").and_return(true) + (@delegator != @base).should be_true + end + + it "is delegated in general" do + @base.should_receive(:"!=").and_return(false) + (@delegator != 42).should be_false + end +end diff --git a/spec/ruby/library/delegate/delegator/not_spec.rb b/spec/ruby/library/delegate/delegator/not_spec.rb new file mode 100644 index 0000000000..50105181c3 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/not_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#!" do + it "is delegated" do + base = mock('base') + delegator = DelegateSpecs::Delegator.new(base) + base.should_receive(:"!").and_return(:foo) + (!delegator).should == :foo + end +end diff --git a/spec/ruby/library/delegate/delegator/private_methods_spec.rb b/spec/ruby/library/delegate/delegator/private_methods_spec.rb new file mode 100644 index 0000000000..7724b8d413 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/private_methods_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#private_methods" do + before :all do + @simple = DelegateSpecs::Simple.new + @delegate = DelegateSpecs::Delegator.new(@simple) + @methods = @delegate.private_methods + end + + it "does not include any method of the delegate object" do # since delegates does not forward private calls + @methods.should_not include :priv + @methods.should_not include :prot + @methods.should_not include :pub + end + + it "includes all private instance methods of the Delegate class" do + @methods.should include :extra_private + end +end diff --git a/spec/ruby/library/delegate/delegator/protected_methods_spec.rb b/spec/ruby/library/delegate/delegator/protected_methods_spec.rb new file mode 100644 index 0000000000..fd7874fb21 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/protected_methods_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#protected_methods" do + before :all do + @simple = DelegateSpecs::Simple.new + @delegate = DelegateSpecs::Delegator.new(@simple) + @methods = @delegate.protected_methods + end + + it "includes protected methods of the delegate object" do + @methods.should include :prot + end + + it "includes protected instance methods of the Delegator class" do + @methods.should include :extra_protected + end +end diff --git a/spec/ruby/library/delegate/delegator/public_methods_spec.rb b/spec/ruby/library/delegate/delegator/public_methods_spec.rb new file mode 100644 index 0000000000..18da16a613 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/public_methods_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#public_methods" do + before :all do + @simple = DelegateSpecs::Simple.new + @delegate = DelegateSpecs::Delegator.new(@simple) + @methods = @delegate.public_methods + end + + it "includes public methods of the delegate object" do + @methods.should include :pub + end + + it "includes public instance methods of the Delegator class" do + @methods.should include :extra + end +end diff --git a/spec/ruby/library/delegate/delegator/send_spec.rb b/spec/ruby/library/delegate/delegator/send_spec.rb new file mode 100644 index 0000000000..3022c2ce91 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/send_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "SimpleDelegator.new" do + before :all do + @simple = DelegateSpecs::Simple.new + @delegate = SimpleDelegator.new(@simple) + end + + it "forwards public method calls" do + @delegate.pub.should == :foo + end + + it "forwards protected method calls" do + ->{ @delegate.prot }.should raise_error( NoMethodError ) + end + + it "doesn't forward private method calls" do + ->{ @delegate.priv }.should raise_error( NoMethodError ) + end + + it "doesn't forward private method calls even via send or __send__" do + ->{ @delegate.send(:priv, 42) }.should raise_error( NoMethodError ) + ->{ @delegate.__send__(:priv, 42) }.should raise_error( NoMethodError ) + end +end diff --git a/spec/ruby/library/delegate/delegator/taint_spec.rb b/spec/ruby/library/delegate/delegator/taint_spec.rb new file mode 100644 index 0000000000..6bf13bb73d --- /dev/null +++ b/spec/ruby/library/delegate/delegator/taint_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#taint" do + before :each do + @delegate = DelegateSpecs::Delegator.new("") + end +end diff --git a/spec/ruby/library/delegate/delegator/tap_spec.rb b/spec/ruby/library/delegate/delegator/tap_spec.rb new file mode 100644 index 0000000000..34a88fa1d5 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/tap_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#tap" do + it "yield the delegator object" do + obj = mock('base') + delegator = DelegateSpecs::Delegator.new(obj) + obj.should_not_receive(:tap) + yielded = [] + delegator.tap do |x| + yielded << x + end + yielded.size.should == 1 + yielded[0].equal?(delegator).should be_true + end +end diff --git a/spec/ruby/library/delegate/delegator/trust_spec.rb b/spec/ruby/library/delegate/delegator/trust_spec.rb new file mode 100644 index 0000000000..f1b81814c5 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/trust_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#trust" do + before :each do + @delegate = DelegateSpecs::Delegator.new([]) + end +end diff --git a/spec/ruby/library/delegate/delegator/untaint_spec.rb b/spec/ruby/library/delegate/delegator/untaint_spec.rb new file mode 100644 index 0000000000..4051fd2629 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/untaint_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#untaint" do + before :each do + @delegate = -> { DelegateSpecs::Delegator.new("") }.call + end +end diff --git a/spec/ruby/library/delegate/delegator/untrust_spec.rb b/spec/ruby/library/delegate/delegator/untrust_spec.rb new file mode 100644 index 0000000000..4f7fa1e582 --- /dev/null +++ b/spec/ruby/library/delegate/delegator/untrust_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "Delegator#untrust" do + before :each do + @delegate = DelegateSpecs::Delegator.new("") + end +end diff --git a/spec/ruby/library/delegate/fixtures/classes.rb b/spec/ruby/library/delegate/fixtures/classes.rb new file mode 100644 index 0000000000..3cb43eb8b1 --- /dev/null +++ b/spec/ruby/library/delegate/fixtures/classes.rb @@ -0,0 +1,60 @@ +require 'delegate' +module DelegateSpecs + class Simple + def pub + :foo + end + + def respond_to_missing?(method, priv=false) + method == :pub_too || + (priv && method == :priv_too) + end + + def method_missing(method, *args) + super unless respond_to_missing?(method, true) + method + end + + def priv(arg=nil) + yield arg if block_given? + [:priv, arg] + end + private :priv + + def prot + :protected + end + protected :prot + end + + module Extra + def extra + :cheese + end + + def extra_private + :bar + end + private :extra_private + + def extra_protected + :baz + end + protected :extra_protected + end + + class Delegator < ::Delegator + attr_accessor :data + + attr_reader :__getobj__ + def __setobj__(o) + @__getobj__ = o + end + + include Extra + end + + class DelegateClass < DelegateClass(Simple) + include Extra + end +end diff --git a/spec/ruby/library/digest/bubblebabble_spec.rb b/spec/ruby/library/digest/bubblebabble_spec.rb new file mode 100644 index 0000000000..bbc11ceec5 --- /dev/null +++ b/spec/ruby/library/digest/bubblebabble_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require 'digest/bubblebabble' + +describe "Digest.bubblebabble" do + it "returns a String" do + Digest.bubblebabble('').should be_an_instance_of(String) + end + + it "returns a String in the Bubble Babble Binary Data Encoding format" do + Digest.bubblebabble('').should == 'xexax' + Digest.bubblebabble('foo').should == 'xinik-zorox' + Digest.bubblebabble('bar').should == 'ximik-cosex' + Digest.bubblebabble('1234567890').should == 'xesef-disof-gytuf-katof-movif-baxux' + end + + it "calls #to_str on an object and returns the bubble babble value of the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return('foo') + Digest.bubblebabble(obj).should == 'xinik-zorox' + end + + it "raises a TypeError when passed nil" do + -> { Digest.bubblebabble(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed an Integer" do + -> { Digest.bubblebabble(9001) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/hexencode_spec.rb b/spec/ruby/library/digest/hexencode_spec.rb new file mode 100644 index 0000000000..4b6db6eaff --- /dev/null +++ b/spec/ruby/library/digest/hexencode_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require 'digest' + +describe "Digest.hexencode" do + before :each do + @string = 'sample string' + @encoded = "73616d706c6520737472696e67" + end + + it "returns '' when passed an empty String" do + Digest.hexencode('').should == '' + end + + it "returns the hex-encoded value of a non-empty String" do + Digest.hexencode(@string).should == @encoded + end + + it "calls #to_str on an object and returns the hex-encoded value of the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(@string) + Digest.hexencode(obj).should == @encoded + end + + it "raises a TypeError when passed nil" do + -> { Digest.hexencode(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed an Integer" do + -> { Digest.hexencode(9001) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/instance/append_spec.rb b/spec/ruby/library/digest/instance/append_spec.rb new file mode 100644 index 0000000000..2499579298 --- /dev/null +++ b/spec/ruby/library/digest/instance/append_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'digest' +require_relative 'shared/update' + +describe "Digest::Instance#<<" do + it_behaves_like :digest_instance_update, :<< +end diff --git a/spec/ruby/library/digest/instance/new_spec.rb b/spec/ruby/library/digest/instance/new_spec.rb new file mode 100644 index 0000000000..3f7939844b --- /dev/null +++ b/spec/ruby/library/digest/instance/new_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../spec_helper' +require 'digest' +require_relative '../md5/shared/constants' + +describe "Digest::Instance#new" do + it "returns a copy of the digest instance" do + digest = Digest::MD5.new + copy = digest.new + copy.should_not.equal?(digest) + end + + it "calls reset" do + digest = Digest::MD5.new + digest << "test" + digest.hexdigest.should != MD5Constants::BlankHexdigest + copy = digest.new + copy.hexdigest.should == MD5Constants::BlankHexdigest + end +end diff --git a/spec/ruby/library/digest/instance/shared/update.rb b/spec/ruby/library/digest/instance/shared/update.rb new file mode 100644 index 0000000000..17779e54a4 --- /dev/null +++ b/spec/ruby/library/digest/instance/shared/update.rb @@ -0,0 +1,8 @@ +describe :digest_instance_update, shared: true do + it "raises a RuntimeError if called" do + c = Class.new do + include Digest::Instance + end + -> { c.new.send(@method, "test") }.should raise_error(RuntimeError) + end +end diff --git a/spec/ruby/library/digest/instance/update_spec.rb b/spec/ruby/library/digest/instance/update_spec.rb new file mode 100644 index 0000000000..3bb4dd7f1b --- /dev/null +++ b/spec/ruby/library/digest/instance/update_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'digest' +require_relative 'shared/update' + +describe "Digest::Instance#update" do + it_behaves_like :digest_instance_update, :update +end diff --git a/spec/ruby/library/digest/md5/append_spec.rb b/spec/ruby/library/digest/md5/append_spec.rb new file mode 100644 index 0000000000..0abdc074a1 --- /dev/null +++ b/spec/ruby/library/digest/md5/append_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::MD5#<<" do + it_behaves_like :md5_update, :<< +end diff --git a/spec/ruby/library/digest/md5/block_length_spec.rb b/spec/ruby/library/digest/md5/block_length_spec.rb new file mode 100644 index 0000000000..14fb050abd --- /dev/null +++ b/spec/ruby/library/digest/md5/block_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#block_length" do + + it "returns the length of digest block" do + cur_digest = Digest::MD5.new + cur_digest.block_length.should == MD5Constants::BlockLength + end + +end diff --git a/spec/ruby/library/digest/md5/digest_bang_spec.rb b/spec/ruby/library/digest/md5/digest_bang_spec.rb new file mode 100644 index 0000000000..7b884a16d9 --- /dev/null +++ b/spec/ruby/library/digest/md5/digest_bang_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#digest!" do + + it "returns a digest and can digest!" do + cur_digest = Digest::MD5.new + cur_digest << MD5Constants::Contents + cur_digest.digest!().should == MD5Constants::Digest + cur_digest.digest().should == MD5Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/md5/digest_length_spec.rb b/spec/ruby/library/digest/md5/digest_length_spec.rb new file mode 100644 index 0000000000..47e071e329 --- /dev/null +++ b/spec/ruby/library/digest/md5/digest_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#digest_length" do + + it "returns the length of computed digests" do + cur_digest = Digest::MD5.new + cur_digest.digest_length.should == MD5Constants::DigestLength + end + +end diff --git a/spec/ruby/library/digest/md5/digest_spec.rb b/spec/ruby/library/digest/md5/digest_spec.rb new file mode 100644 index 0000000000..d9bbc45ee2 --- /dev/null +++ b/spec/ruby/library/digest/md5/digest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#digest" do + + it "returns a digest" do + cur_digest = Digest::MD5.new + cur_digest.digest().should == MD5Constants::BlankDigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.digest(MD5Constants::Contents).should == MD5Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.digest(MD5Constants::Contents).should == MD5Constants::Digest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.digest.should == MD5Constants::BlankDigest + end + +end + +describe "Digest::MD5.digest" do + + it "returns a digest" do + Digest::MD5.digest(MD5Constants::Contents).should == MD5Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + Digest::MD5.digest(MD5Constants::Contents).should == MD5Constants::Digest + Digest::MD5.digest("").should == MD5Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/md5/equal_spec.rb b/spec/ruby/library/digest/md5/equal_spec.rb new file mode 100644 index 0000000000..b0e36564cd --- /dev/null +++ b/spec/ruby/library/digest/md5/equal_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#==" do + + it "equals itself" do + cur_digest = Digest::MD5.new + cur_digest.should == cur_digest + end + + it "equals the string representing its hexdigest" do + cur_digest = Digest::MD5.new + cur_digest.should == MD5Constants::BlankHexdigest + end + + it "equals the appropriate object that responds to to_str" do + # blank digest + cur_digest = Digest::MD5.new + obj = mock(MD5Constants::BlankHexdigest) + obj.should_receive(:to_str).and_return(MD5Constants::BlankHexdigest) + cur_digest.should == obj + + # non-blank digest + cur_digest = Digest::MD5.new + cur_digest << "test" + d_value = cur_digest.hexdigest + (obj = mock(d_value)).should_receive(:to_str).and_return(d_value) + cur_digest.should == obj + end + + it "equals the same digest for a different object" do + cur_digest = Digest::MD5.new + cur_digest2 = Digest::MD5.new + cur_digest.should == cur_digest2 + end + +end diff --git a/spec/ruby/library/digest/md5/file_spec.rb b/spec/ruby/library/digest/md5/file_spec.rb new file mode 100644 index 0000000000..0c8d12cbc9 --- /dev/null +++ b/spec/ruby/library/digest/md5/file_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative '../../../core/file/shared/read' + +describe "Digest::MD5.file" do + + describe "when passed a path to a file that exists" do + before :each do + @file = tmp("md5_temp") + touch(@file, 'wb') {|f| f.write MD5Constants::Contents } + end + + after :each do + rm_r @file + end + + it "returns a Digest::MD5 object" do + Digest::MD5.file(@file).should be_kind_of(Digest::MD5) + end + + it "returns a Digest::MD5 object with the correct digest" do + Digest::MD5.file(@file).digest.should == MD5Constants::Digest + end + + it "calls #to_str on an object and returns the Digest::MD5 with the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(@file) + result = Digest::MD5.file(obj) + result.should be_kind_of(Digest::MD5) + result.digest.should == MD5Constants::Digest + end + end + + it_behaves_like :file_read_directory, :file, Digest::MD5 + + it "raises a Errno::ENOENT when passed a path that does not exist" do + -> { Digest::MD5.file("") }.should raise_error(Errno::ENOENT) + end + + it "raises a TypeError when passed nil" do + -> { Digest::MD5.file(nil) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/md5/hexdigest_bang_spec.rb b/spec/ruby/library/digest/md5/hexdigest_bang_spec.rb new file mode 100644 index 0000000000..a953eb3b4c --- /dev/null +++ b/spec/ruby/library/digest/md5/hexdigest_bang_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#hexdigest!" do + + it "returns a hexdigest and resets the state" do + cur_digest = Digest::MD5.new + + cur_digest << MD5Constants::Contents + cur_digest.hexdigest!.should == MD5Constants::Hexdigest + cur_digest.hexdigest.should == MD5Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/md5/hexdigest_spec.rb b/spec/ruby/library/digest/md5/hexdigest_spec.rb new file mode 100644 index 0000000000..03ead68b82 --- /dev/null +++ b/spec/ruby/library/digest/md5/hexdigest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#hexdigest" do + + it "returns a hexdigest" do + cur_digest = Digest::MD5.new + cur_digest.hexdigest.should == MD5Constants::BlankHexdigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.hexdigest.should == MD5Constants::BlankHexdigest + end + +end + +describe "Digest::MD5.hexdigest" do + + it "returns a hexdigest" do + Digest::MD5.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + Digest::MD5.hexdigest(MD5Constants::Contents).should == MD5Constants::Hexdigest + Digest::MD5.hexdigest("").should == MD5Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/md5/inspect_spec.rb b/spec/ruby/library/digest/md5/inspect_spec.rb new file mode 100644 index 0000000000..decc86fba5 --- /dev/null +++ b/spec/ruby/library/digest/md5/inspect_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#inspect" do + + it "returns a Ruby object representation" do + cur_digest = Digest::MD5.new + cur_digest.inspect.should == "#<#{MD5Constants::Klass}: #{cur_digest.hexdigest()}>" + end + +end diff --git a/spec/ruby/library/digest/md5/length_spec.rb b/spec/ruby/library/digest/md5/length_spec.rb new file mode 100644 index 0000000000..b05b2a20fd --- /dev/null +++ b/spec/ruby/library/digest/md5/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::MD5#length" do + it_behaves_like :md5_length, :length +end diff --git a/spec/ruby/library/digest/md5/reset_spec.rb b/spec/ruby/library/digest/md5/reset_spec.rb new file mode 100644 index 0000000000..c937844f38 --- /dev/null +++ b/spec/ruby/library/digest/md5/reset_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::MD5#reset" do + + it "returns digest state to initial conditions" do + cur_digest = Digest::MD5.new + cur_digest.update MD5Constants::Contents + cur_digest.digest().should_not == MD5Constants::BlankDigest + cur_digest.reset + cur_digest.digest().should == MD5Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/md5/shared/constants.rb b/spec/ruby/library/digest/md5/shared/constants.rb new file mode 100644 index 0000000000..664dd18e9c --- /dev/null +++ b/spec/ruby/library/digest/md5/shared/constants.rb @@ -0,0 +1,17 @@ +# encoding: binary +require 'digest/md5' + +module MD5Constants + + Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + + Klass = ::Digest::MD5 + BlockLength = 64 + DigestLength = 16 + BlankDigest = "\324\035\214\331\217\000\262\004\351\200\t\230\354\370B~" + Digest = "\2473\267qw\276\364\343\345\320\304\350\313\314\217n" + BlankHexdigest = "d41d8cd98f00b204e9800998ecf8427e" + Hexdigest = "a733b77177bef4e3e5d0c4e8cbcc8f6e" + Base64digest = "pzO3cXe+9OPl0MToy8yPbg==" + +end diff --git a/spec/ruby/library/digest/md5/shared/length.rb b/spec/ruby/library/digest/md5/shared/length.rb new file mode 100644 index 0000000000..c5b2b97b58 --- /dev/null +++ b/spec/ruby/library/digest/md5/shared/length.rb @@ -0,0 +1,8 @@ +describe :md5_length, shared: true do + it "returns the length of the digest" do + cur_digest = Digest::MD5.new + cur_digest.send(@method).should == MD5Constants::BlankDigest.size + cur_digest << MD5Constants::Contents + cur_digest.send(@method).should == MD5Constants::Digest.size + end +end diff --git a/spec/ruby/library/digest/md5/shared/update.rb b/spec/ruby/library/digest/md5/shared/update.rb new file mode 100644 index 0000000000..be8622aed5 --- /dev/null +++ b/spec/ruby/library/digest/md5/shared/update.rb @@ -0,0 +1,7 @@ +describe :md5_update, shared: true do + it "can update" do + cur_digest = Digest::MD5.new + cur_digest.send @method, MD5Constants::Contents + cur_digest.digest.should == MD5Constants::Digest + end +end diff --git a/spec/ruby/library/digest/md5/size_spec.rb b/spec/ruby/library/digest/md5/size_spec.rb new file mode 100644 index 0000000000..22e3272d36 --- /dev/null +++ b/spec/ruby/library/digest/md5/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::MD5#size" do + it_behaves_like :md5_length, :size +end diff --git a/spec/ruby/library/digest/md5/to_s_spec.rb b/spec/ruby/library/digest/md5/to_s_spec.rb new file mode 100644 index 0000000000..78d53d6967 --- /dev/null +++ b/spec/ruby/library/digest/md5/to_s_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' + +require 'digest/md5' + +require_relative 'shared/constants' + +describe "Digest::MD5#to_s" do + + it "returns a hexdigest" do + cur_digest = Digest::MD5.new + cur_digest.to_s.should == MD5Constants::BlankHexdigest + end + + it "does not change the internal state" do + cur_digest = Digest::MD5.new + cur_digest.to_s.should == MD5Constants::BlankHexdigest + cur_digest.to_s.should == MD5Constants::BlankHexdigest + + cur_digest << MD5Constants::Contents + cur_digest.to_s.should == MD5Constants::Hexdigest + cur_digest.to_s.should == MD5Constants::Hexdigest + end + +end diff --git a/spec/ruby/library/digest/md5/update_spec.rb b/spec/ruby/library/digest/md5/update_spec.rb new file mode 100644 index 0000000000..4773db308c --- /dev/null +++ b/spec/ruby/library/digest/md5/update_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::MD5#update" do + it_behaves_like :md5_update, :update +end diff --git a/spec/ruby/library/digest/sha1/digest_spec.rb b/spec/ruby/library/digest/sha1/digest_spec.rb new file mode 100644 index 0000000000..03f805336f --- /dev/null +++ b/spec/ruby/library/digest/sha1/digest_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA1#digest" do + + it "returns a digest" do + cur_digest = Digest::SHA1.new + cur_digest.digest().should == SHA1Constants::BlankDigest + cur_digest.digest(SHA1Constants::Contents).should == SHA1Constants::Digest + end + +end + +describe "Digest::SHA1.digest" do + + it "returns a digest" do + Digest::SHA1.digest(SHA1Constants::Contents).should == SHA1Constants::Digest + end + +end diff --git a/spec/ruby/library/digest/sha1/file_spec.rb b/spec/ruby/library/digest/sha1/file_spec.rb new file mode 100644 index 0000000000..9c15f5b02f --- /dev/null +++ b/spec/ruby/library/digest/sha1/file_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative '../../../core/file/shared/read' + +describe "Digest::SHA1.file" do + + describe "when passed a path to a file that exists" do + before :each do + @file = tmp("md5_temp") + touch(@file, 'wb') {|f| f.write SHA1Constants::Contents } + end + + after :each do + rm_r @file + end + + it "returns a Digest::SHA1 object" do + Digest::SHA1.file(@file).should be_kind_of(Digest::SHA1) + end + + it "returns a Digest::SHA1 object with the correct digest" do + Digest::SHA1.file(@file).digest.should == SHA1Constants::Digest + end + + it "calls #to_str on an object and returns the Digest::SHA1 with the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(@file) + result = Digest::SHA1.file(obj) + result.should be_kind_of(Digest::SHA1) + result.digest.should == SHA1Constants::Digest + end + end + + it_behaves_like :file_read_directory, :file, Digest::SHA1 + + it "raises a Errno::ENOENT when passed a path that does not exist" do + -> { Digest::SHA1.file("") }.should raise_error(Errno::ENOENT) + end + + it "raises a TypeError when passed nil" do + -> { Digest::SHA1.file(nil) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/sha1/shared/constants.rb b/spec/ruby/library/digest/sha1/shared/constants.rb new file mode 100644 index 0000000000..d77c05a968 --- /dev/null +++ b/spec/ruby/library/digest/sha1/shared/constants.rb @@ -0,0 +1,18 @@ +# encoding: binary + +require 'digest/sha1' + +module SHA1Constants + + Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + + Klass = ::Digest::SHA1 + BlockLength = 64 + DigestLength = 20 + BlankDigest = "\3329\243\356^kK\r2U\277\357\225`\030\220\257\330\a\t" + Digest = "X!\255b\323\035\352\314a|q\344+\376\317\361V9\324\343" + BlankHexdigest = "da39a3ee5e6b4b0d3255bfef95601890afd80709" + Hexdigest = "5821ad62d31deacc617c71e42bfecff15639d4e3" + Base64digest = "WCGtYtMd6sxhfHHkK/7P8VY51OM=" + +end diff --git a/spec/ruby/library/digest/sha2/hexdigest_spec.rb b/spec/ruby/library/digest/sha2/hexdigest_spec.rb new file mode 100644 index 0000000000..79beca5788 --- /dev/null +++ b/spec/ruby/library/digest/sha2/hexdigest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative '../sha256/shared/constants' + +describe "Digest::SHA2#hexdigest" do + + it "returns a SHA256 hexdigest by default" do + cur_digest = Digest::SHA2.new + cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest + end + +end + +describe "Digest::SHA2.hexdigest" do + + it "returns a SHA256 hexdigest by default" do + Digest::SHA2.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA2.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + Digest::SHA2.hexdigest("").should == SHA256Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha256/append_spec.rb b/spec/ruby/library/digest/sha256/append_spec.rb new file mode 100644 index 0000000000..ab594c105f --- /dev/null +++ b/spec/ruby/library/digest/sha256/append_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::SHA256#<<" do + it_behaves_like :sha256_update, :<< +end diff --git a/spec/ruby/library/digest/sha256/block_length_spec.rb b/spec/ruby/library/digest/sha256/block_length_spec.rb new file mode 100644 index 0000000000..1e29e832cf --- /dev/null +++ b/spec/ruby/library/digest/sha256/block_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#block_length" do + + it "returns the length of digest block" do + cur_digest = Digest::SHA256.new + cur_digest.block_length.should == SHA256Constants::BlockLength + end + +end diff --git a/spec/ruby/library/digest/sha256/digest_bang_spec.rb b/spec/ruby/library/digest/sha256/digest_bang_spec.rb new file mode 100644 index 0000000000..690442221a --- /dev/null +++ b/spec/ruby/library/digest/sha256/digest_bang_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#digest!" do + + it "returns a digest and can digest!" do + cur_digest = Digest::SHA256.new + cur_digest << SHA256Constants::Contents + cur_digest.digest!().should == SHA256Constants::Digest + cur_digest.digest().should == SHA256Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha256/digest_length_spec.rb b/spec/ruby/library/digest/sha256/digest_length_spec.rb new file mode 100644 index 0000000000..b5c8958e84 --- /dev/null +++ b/spec/ruby/library/digest/sha256/digest_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#digest_length" do + + it "returns the length of computed digests" do + cur_digest = Digest::SHA256.new + cur_digest.digest_length.should == SHA256Constants::DigestLength + end + +end diff --git a/spec/ruby/library/digest/sha256/digest_spec.rb b/spec/ruby/library/digest/sha256/digest_spec.rb new file mode 100644 index 0000000000..1e79f25627 --- /dev/null +++ b/spec/ruby/library/digest/sha256/digest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#digest" do + + it "returns a digest" do + cur_digest = Digest::SHA256.new + cur_digest.digest().should == SHA256Constants::BlankDigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.digest(SHA256Constants::Contents).should == SHA256Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.digest(SHA256Constants::Contents).should == SHA256Constants::Digest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.digest.should == SHA256Constants::BlankDigest + end + +end + +describe "Digest::SHA256.digest" do + + it "returns a digest" do + Digest::SHA256.digest(SHA256Constants::Contents).should == SHA256Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA256.digest(SHA256Constants::Contents).should == SHA256Constants::Digest + Digest::SHA256.digest("").should == SHA256Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha256/equal_spec.rb b/spec/ruby/library/digest/sha256/equal_spec.rb new file mode 100644 index 0000000000..84662aa068 --- /dev/null +++ b/spec/ruby/library/digest/sha256/equal_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#==" do + + it "equals itself" do + cur_digest = Digest::SHA256.new + cur_digest.should == cur_digest + end + + it "equals the string representing its hexdigest" do + cur_digest = Digest::SHA256.new + cur_digest.should == SHA256Constants::BlankHexdigest + end + + it "equals the appropriate object that responds to to_str" do + # blank digest + cur_digest = Digest::SHA256.new + (obj = mock(SHA256Constants::BlankHexdigest)).should_receive(:to_str).and_return(SHA256Constants::BlankHexdigest) + cur_digest.should == obj + + # non-blank digest + cur_digest = Digest::SHA256.new + cur_digest << "test" + d_value = cur_digest.hexdigest + (obj = mock(d_value)).should_receive(:to_str).and_return(d_value) + cur_digest.should == obj + end + + it "equals the same digest for a different object" do + cur_digest = Digest::SHA256.new + cur_digest2 = Digest::SHA256.new + cur_digest.should == cur_digest2 + end + +end diff --git a/spec/ruby/library/digest/sha256/file_spec.rb b/spec/ruby/library/digest/sha256/file_spec.rb new file mode 100644 index 0000000000..8cbc5a2755 --- /dev/null +++ b/spec/ruby/library/digest/sha256/file_spec.rb @@ -0,0 +1,47 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative '../../../core/file/shared/read' + +describe "Digest::SHA256.file" do + + describe "when passed a path to a file that exists" do + before :each do + @file = tmp("md5_temp") + touch(@file, 'wb') {|f| f.write SHA256Constants::Contents } + end + + after :each do + rm_r @file + end + + it "returns a Digest::SHA256 object" do + Digest::SHA256.file(@file).should be_kind_of(Digest::SHA256) + end + + it "returns a Digest::SHA256 object with the correct digest" do + Digest::SHA256.file(@file).digest.should == SHA256Constants::Digest + end + + it "can be used with frozen-string-literal" do + ruby_exe("require 'digest'; puts Digest::SHA256.file(#{@file.inspect}).digest.inspect", options: "--enable=frozen-string-literal").chomp.should == SHA256Constants::Digest.inspect + end + + it "calls #to_str on an object and returns the Digest::SHA256 with the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(@file) + result = Digest::SHA256.file(obj) + result.should be_kind_of(Digest::SHA256) + result.digest.should == SHA256Constants::Digest + end + end + + it_behaves_like :file_read_directory, :file, Digest::SHA256 + + it "raises a Errno::ENOENT when passed a path that does not exist" do + -> { Digest::SHA256.file("") }.should raise_error(Errno::ENOENT) + end + + it "raises a TypeError when passed nil" do + -> { Digest::SHA256.file(nil) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb b/spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb new file mode 100644 index 0000000000..1acd8043b3 --- /dev/null +++ b/spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#hexdigest!" do + + it "returns a hexdigest and resets the state" do + cur_digest = Digest::SHA256.new + + cur_digest << SHA256Constants::Contents + cur_digest.hexdigest!.should == SHA256Constants::Hexdigest + cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha256/hexdigest_spec.rb b/spec/ruby/library/digest/sha256/hexdigest_spec.rb new file mode 100644 index 0000000000..4f748b33b4 --- /dev/null +++ b/spec/ruby/library/digest/sha256/hexdigest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#hexdigest" do + + it "returns a hexdigest" do + cur_digest = Digest::SHA256.new + cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.hexdigest.should == SHA256Constants::BlankHexdigest + end + +end + +describe "Digest::SHA256.hexdigest" do + + it "returns a hexdigest" do + Digest::SHA256.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA256.hexdigest(SHA256Constants::Contents).should == SHA256Constants::Hexdigest + Digest::SHA256.hexdigest("").should == SHA256Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha256/inspect_spec.rb b/spec/ruby/library/digest/sha256/inspect_spec.rb new file mode 100644 index 0000000000..ed606e4517 --- /dev/null +++ b/spec/ruby/library/digest/sha256/inspect_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#inspect" do + + it "returns a Ruby object representation" do + cur_digest = Digest::SHA256.new + cur_digest.inspect.should == "#<#{SHA256Constants::Klass}: #{cur_digest.hexdigest()}>" + end + +end diff --git a/spec/ruby/library/digest/sha256/length_spec.rb b/spec/ruby/library/digest/sha256/length_spec.rb new file mode 100644 index 0000000000..181ac564ad --- /dev/null +++ b/spec/ruby/library/digest/sha256/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::SHA256#length" do + it_behaves_like :sha256_length, :length +end diff --git a/spec/ruby/library/digest/sha256/reset_spec.rb b/spec/ruby/library/digest/sha256/reset_spec.rb new file mode 100644 index 0000000000..f0eb4faea6 --- /dev/null +++ b/spec/ruby/library/digest/sha256/reset_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#reset" do + + it "returns digest state to initial conditions" do + cur_digest = Digest::SHA256.new + cur_digest.update SHA256Constants::Contents + cur_digest.digest().should_not == SHA256Constants::BlankDigest + cur_digest.reset + cur_digest.digest().should == SHA256Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha256/shared/constants.rb b/spec/ruby/library/digest/sha256/shared/constants.rb new file mode 100644 index 0000000000..afe8f11426 --- /dev/null +++ b/spec/ruby/library/digest/sha256/shared/constants.rb @@ -0,0 +1,18 @@ +# encoding: binary + +require 'digest/sha2' + +module SHA256Constants + + Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + + Klass = ::Digest::SHA256 + BlockLength = 64 + DigestLength = 32 + BlankDigest = "\343\260\304B\230\374\034\024\232\373\364\310\231o\271$'\256A\344d\233\223L\244\225\231\exR\270U" + Digest = "\230b\265\344_\337\357\337\242\004\314\311A\211jb\350\373\254\370\365M\230B\002\372\020j\as\270\376" + BlankHexdigest = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + Hexdigest = "9862b5e45fdfefdfa204ccc941896a62e8fbacf8f54d984202fa106a0773b8fe" + Base64digest = "mGK15F/f79+iBMzJQYlqYuj7rPj1TZhCAvoQagdzuP4=" + +end diff --git a/spec/ruby/library/digest/sha256/shared/length.rb b/spec/ruby/library/digest/sha256/shared/length.rb new file mode 100644 index 0000000000..996673a5bd --- /dev/null +++ b/spec/ruby/library/digest/sha256/shared/length.rb @@ -0,0 +1,8 @@ +describe :sha256_length, shared: true do + it "returns the length of the digest" do + cur_digest = Digest::SHA256.new + cur_digest.send(@method).should == SHA256Constants::BlankDigest.size + cur_digest << SHA256Constants::Contents + cur_digest.send(@method).should == SHA256Constants::Digest.size + end +end diff --git a/spec/ruby/library/digest/sha256/shared/update.rb b/spec/ruby/library/digest/sha256/shared/update.rb new file mode 100644 index 0000000000..0edc07935b --- /dev/null +++ b/spec/ruby/library/digest/sha256/shared/update.rb @@ -0,0 +1,7 @@ +describe :sha256_update, shared: true do + it "can update" do + cur_digest = Digest::SHA256.new + cur_digest.send @method, SHA256Constants::Contents + cur_digest.digest.should == SHA256Constants::Digest + end +end diff --git a/spec/ruby/library/digest/sha256/size_spec.rb b/spec/ruby/library/digest/sha256/size_spec.rb new file mode 100644 index 0000000000..1028263342 --- /dev/null +++ b/spec/ruby/library/digest/sha256/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::SHA256#size" do + it_behaves_like :sha256_length, :size +end diff --git a/spec/ruby/library/digest/sha256/to_s_spec.rb b/spec/ruby/library/digest/sha256/to_s_spec.rb new file mode 100644 index 0000000000..8bedee3f98 --- /dev/null +++ b/spec/ruby/library/digest/sha256/to_s_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA256#to_s" do + + it "returns a hexdigest" do + cur_digest = Digest::SHA256.new + cur_digest.to_s.should == SHA256Constants::BlankHexdigest + end + + it "does not change the internal state" do + cur_digest = Digest::SHA256.new + cur_digest.to_s.should == SHA256Constants::BlankHexdigest + cur_digest.to_s.should == SHA256Constants::BlankHexdigest + + cur_digest << SHA256Constants::Contents + cur_digest.to_s.should == SHA256Constants::Hexdigest + cur_digest.to_s.should == SHA256Constants::Hexdigest + end + +end diff --git a/spec/ruby/library/digest/sha256/update_spec.rb b/spec/ruby/library/digest/sha256/update_spec.rb new file mode 100644 index 0000000000..92316eb752 --- /dev/null +++ b/spec/ruby/library/digest/sha256/update_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::SHA256#update" do + it_behaves_like :sha256_update, :update +end diff --git a/spec/ruby/library/digest/sha384/append_spec.rb b/spec/ruby/library/digest/sha384/append_spec.rb new file mode 100644 index 0000000000..94c036cc3f --- /dev/null +++ b/spec/ruby/library/digest/sha384/append_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::SHA384#<<" do + it_behaves_like :sha384_update, :<< +end diff --git a/spec/ruby/library/digest/sha384/block_length_spec.rb b/spec/ruby/library/digest/sha384/block_length_spec.rb new file mode 100644 index 0000000000..dff645ffb9 --- /dev/null +++ b/spec/ruby/library/digest/sha384/block_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#block_length" do + + it "returns the length of digest block" do + cur_digest = Digest::SHA384.new + cur_digest.block_length.should == SHA384Constants::BlockLength + end + +end diff --git a/spec/ruby/library/digest/sha384/digest_bang_spec.rb b/spec/ruby/library/digest/sha384/digest_bang_spec.rb new file mode 100644 index 0000000000..8711913deb --- /dev/null +++ b/spec/ruby/library/digest/sha384/digest_bang_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#digest!" do + + it "returns a digest and can digest!" do + cur_digest = Digest::SHA384.new + cur_digest << SHA384Constants::Contents + cur_digest.digest!().should == SHA384Constants::Digest + cur_digest.digest().should == SHA384Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha384/digest_length_spec.rb b/spec/ruby/library/digest/sha384/digest_length_spec.rb new file mode 100644 index 0000000000..4067dd34af --- /dev/null +++ b/spec/ruby/library/digest/sha384/digest_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#digest_length" do + + it "returns the length of computed digests" do + cur_digest = Digest::SHA384.new + cur_digest.digest_length.should == SHA384Constants::DigestLength + end + +end diff --git a/spec/ruby/library/digest/sha384/digest_spec.rb b/spec/ruby/library/digest/sha384/digest_spec.rb new file mode 100644 index 0000000000..d0e2825934 --- /dev/null +++ b/spec/ruby/library/digest/sha384/digest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#digest" do + + it "returns a digest" do + cur_digest = Digest::SHA384.new + cur_digest.digest().should == SHA384Constants::BlankDigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.digest(SHA384Constants::Contents).should == SHA384Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.digest(SHA384Constants::Contents).should == SHA384Constants::Digest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.digest.should == SHA384Constants::BlankDigest + end + +end + +describe "Digest::SHA384.digest" do + + it "returns a digest" do + Digest::SHA384.digest(SHA384Constants::Contents).should == SHA384Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA384.digest(SHA384Constants::Contents).should == SHA384Constants::Digest + Digest::SHA384.digest("").should == SHA384Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha384/equal_spec.rb b/spec/ruby/library/digest/sha384/equal_spec.rb new file mode 100644 index 0000000000..5d3483d79a --- /dev/null +++ b/spec/ruby/library/digest/sha384/equal_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#==" do + + it "equals itself" do + cur_digest = Digest::SHA384.new + cur_digest.should == cur_digest + end + + it "equals the string representing its hexdigest" do + cur_digest = Digest::SHA384.new + cur_digest.should == SHA384Constants::BlankHexdigest + end + + it "equals the appropriate object that responds to to_str" do + # blank digest + cur_digest = Digest::SHA384.new + (obj = mock(SHA384Constants::BlankHexdigest)).should_receive(:to_str).and_return(SHA384Constants::BlankHexdigest) + cur_digest.should == obj + + # non-blank digest + cur_digest = Digest::SHA384.new + cur_digest << "test" + d_value = cur_digest.hexdigest + (obj = mock(d_value)).should_receive(:to_str).and_return(d_value) + cur_digest.should == obj + end + + it "equals the same digest for a different object" do + cur_digest = Digest::SHA384.new + cur_digest2 = Digest::SHA384.new + cur_digest.should == cur_digest2 + end + +end diff --git a/spec/ruby/library/digest/sha384/file_spec.rb b/spec/ruby/library/digest/sha384/file_spec.rb new file mode 100644 index 0000000000..8556f10175 --- /dev/null +++ b/spec/ruby/library/digest/sha384/file_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative '../../../core/file/shared/read' + +describe "Digest::SHA384.file" do + + describe "when passed a path to a file that exists" do + before :each do + @file = tmp("md5_temp") + touch(@file, 'wb') {|f| f.write SHA384Constants::Contents } + end + + after :each do + rm_r @file + end + + it "returns a Digest::SHA384 object" do + Digest::SHA384.file(@file).should be_kind_of(Digest::SHA384) + end + + it "returns a Digest::SHA384 object with the correct digest" do + Digest::SHA384.file(@file).digest.should == SHA384Constants::Digest + end + + it "calls #to_str on an object and returns the Digest::SHA384 with the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(@file) + result = Digest::SHA384.file(obj) + result.should be_kind_of(Digest::SHA384) + result.digest.should == SHA384Constants::Digest + end + end + + it_behaves_like :file_read_directory, :file, Digest::SHA384 + + it "raises a Errno::ENOENT when passed a path that does not exist" do + -> { Digest::SHA384.file("") }.should raise_error(Errno::ENOENT) + end + + it "raises a TypeError when passed nil" do + -> { Digest::SHA384.file(nil) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb b/spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb new file mode 100644 index 0000000000..8efceec3eb --- /dev/null +++ b/spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#hexdigest!" do + + it "returns a hexdigest and resets the state" do + cur_digest = Digest::SHA384.new + + cur_digest << SHA384Constants::Contents + cur_digest.hexdigest!.should == SHA384Constants::Hexdigest + cur_digest.hexdigest.should == SHA384Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha384/hexdigest_spec.rb b/spec/ruby/library/digest/sha384/hexdigest_spec.rb new file mode 100644 index 0000000000..07ea05c541 --- /dev/null +++ b/spec/ruby/library/digest/sha384/hexdigest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#hexdigest" do + + it "returns a hexdigest" do + cur_digest = Digest::SHA384.new + cur_digest.hexdigest.should == SHA384Constants::BlankHexdigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.hexdigest.should == SHA384Constants::BlankHexdigest + end + +end + +describe "Digest::SHA384.hexdigest" do + + it "returns a hexdigest" do + Digest::SHA384.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA384.hexdigest(SHA384Constants::Contents).should == SHA384Constants::Hexdigest + Digest::SHA384.hexdigest("").should == SHA384Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha384/inspect_spec.rb b/spec/ruby/library/digest/sha384/inspect_spec.rb new file mode 100644 index 0000000000..8f9f946cc5 --- /dev/null +++ b/spec/ruby/library/digest/sha384/inspect_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#inspect" do + + it "returns a Ruby object representation" do + cur_digest = Digest::SHA384.new + cur_digest.inspect.should == "#<#{SHA384Constants::Klass}: #{cur_digest.hexdigest()}>" + end + +end diff --git a/spec/ruby/library/digest/sha384/length_spec.rb b/spec/ruby/library/digest/sha384/length_spec.rb new file mode 100644 index 0000000000..33fed492ef --- /dev/null +++ b/spec/ruby/library/digest/sha384/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::SHA384#length" do + it_behaves_like :sha384_length, :length +end diff --git a/spec/ruby/library/digest/sha384/reset_spec.rb b/spec/ruby/library/digest/sha384/reset_spec.rb new file mode 100644 index 0000000000..991b90903d --- /dev/null +++ b/spec/ruby/library/digest/sha384/reset_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#reset" do + + it "returns digest state to initial conditions" do + cur_digest = Digest::SHA384.new + cur_digest.update SHA384Constants::Contents + cur_digest.digest().should_not == SHA384Constants::BlankDigest + cur_digest.reset + cur_digest.digest().should == SHA384Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha384/shared/constants.rb b/spec/ruby/library/digest/sha384/shared/constants.rb new file mode 100644 index 0000000000..a78d571d26 --- /dev/null +++ b/spec/ruby/library/digest/sha384/shared/constants.rb @@ -0,0 +1,19 @@ +# encoding: binary + +require 'digest/sha2' + +module SHA384Constants + + Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + + + Klass = ::Digest::SHA384 + BlockLength = 128 + DigestLength = 48 + BlankDigest = "8\260`\247Q\254\2268L\3312~\261\261\343j!\375\267\021\024\276\aCL\f\307\277c\366\341\332'N\336\277\347oe\373\325\032\322\361H\230\271[" + Digest = "B&\266:\314\216z\361!TD\001{`\355\323\320MW%\270\272\0034n\034\026g\a\217\"\333s\202\275\002Y*\217]\207u\f\034\244\231\266f" + BlankHexdigest = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + Hexdigest = "4226b63acc8e7af1215444017b60edd3d04d5725b8ba03346e1c1667078f22db7382bd02592a8f5d87750c1ca499b666" + Base64digest = "Qia2OsyOevEhVEQBe2Dt09BNVyW4ugM0bhwWZwePIttzgr0CWSqPXYd1DBykmbZm" + +end diff --git a/spec/ruby/library/digest/sha384/shared/length.rb b/spec/ruby/library/digest/sha384/shared/length.rb new file mode 100644 index 0000000000..0c88288bcf --- /dev/null +++ b/spec/ruby/library/digest/sha384/shared/length.rb @@ -0,0 +1,8 @@ +describe :sha384_length, shared: true do + it "returns the length of the digest" do + cur_digest = Digest::SHA384.new + cur_digest.send(@method).should == SHA384Constants::BlankDigest.size + cur_digest << SHA384Constants::Contents + cur_digest.send(@method).should == SHA384Constants::Digest.size + end +end diff --git a/spec/ruby/library/digest/sha384/shared/update.rb b/spec/ruby/library/digest/sha384/shared/update.rb new file mode 100644 index 0000000000..1c6e31cf6a --- /dev/null +++ b/spec/ruby/library/digest/sha384/shared/update.rb @@ -0,0 +1,7 @@ +describe :sha384_update, shared: true do + it "can update" do + cur_digest = Digest::SHA384.new + cur_digest.send @method, SHA384Constants::Contents + cur_digest.digest.should == SHA384Constants::Digest + end +end diff --git a/spec/ruby/library/digest/sha384/size_spec.rb b/spec/ruby/library/digest/sha384/size_spec.rb new file mode 100644 index 0000000000..4c3b14f7a0 --- /dev/null +++ b/spec/ruby/library/digest/sha384/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::SHA384#size" do + it_behaves_like :sha384_length, :size +end diff --git a/spec/ruby/library/digest/sha384/to_s_spec.rb b/spec/ruby/library/digest/sha384/to_s_spec.rb new file mode 100644 index 0000000000..68ea9c013f --- /dev/null +++ b/spec/ruby/library/digest/sha384/to_s_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA384#to_s" do + + it "returns a hexdigest" do + cur_digest = Digest::SHA384.new + cur_digest.to_s.should == SHA384Constants::BlankHexdigest + end + + it "does not change the internal state" do + cur_digest = Digest::SHA384.new + cur_digest.to_s.should == SHA384Constants::BlankHexdigest + cur_digest.to_s.should == SHA384Constants::BlankHexdigest + + cur_digest << SHA384Constants::Contents + cur_digest.to_s.should == SHA384Constants::Hexdigest + cur_digest.to_s.should == SHA384Constants::Hexdigest + end + +end diff --git a/spec/ruby/library/digest/sha384/update_spec.rb b/spec/ruby/library/digest/sha384/update_spec.rb new file mode 100644 index 0000000000..a1d0dd6068 --- /dev/null +++ b/spec/ruby/library/digest/sha384/update_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::SHA384#update" do + it_behaves_like :sha384_update, :update +end diff --git a/spec/ruby/library/digest/sha512/append_spec.rb b/spec/ruby/library/digest/sha512/append_spec.rb new file mode 100644 index 0000000000..9106e9685d --- /dev/null +++ b/spec/ruby/library/digest/sha512/append_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::SHA512#<<" do + it_behaves_like :sha512_update, :<< +end diff --git a/spec/ruby/library/digest/sha512/block_length_spec.rb b/spec/ruby/library/digest/sha512/block_length_spec.rb new file mode 100644 index 0000000000..947af841dd --- /dev/null +++ b/spec/ruby/library/digest/sha512/block_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#block_length" do + + it "returns the length of digest block" do + cur_digest = Digest::SHA512.new + cur_digest.block_length.should == SHA512Constants::BlockLength + end + +end diff --git a/spec/ruby/library/digest/sha512/digest_bang_spec.rb b/spec/ruby/library/digest/sha512/digest_bang_spec.rb new file mode 100644 index 0000000000..981570b907 --- /dev/null +++ b/spec/ruby/library/digest/sha512/digest_bang_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#digest!" do + + it "returns a digest and can digest!" do + cur_digest = Digest::SHA512.new + cur_digest << SHA512Constants::Contents + cur_digest.digest!().should == SHA512Constants::Digest + cur_digest.digest().should == SHA512Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha512/digest_length_spec.rb b/spec/ruby/library/digest/sha512/digest_length_spec.rb new file mode 100644 index 0000000000..ff5956dd75 --- /dev/null +++ b/spec/ruby/library/digest/sha512/digest_length_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#digest_length" do + + it "returns the length of computed digests" do + cur_digest = Digest::SHA512.new + cur_digest.digest_length.should == SHA512Constants::DigestLength + end + +end diff --git a/spec/ruby/library/digest/sha512/digest_spec.rb b/spec/ruby/library/digest/sha512/digest_spec.rb new file mode 100644 index 0000000000..092efccc62 --- /dev/null +++ b/spec/ruby/library/digest/sha512/digest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#digest" do + + it "returns a digest" do + cur_digest = Digest::SHA512.new + cur_digest.digest().should == SHA512Constants::BlankDigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.digest(SHA512Constants::Contents).should == SHA512Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.digest(SHA512Constants::Contents).should == SHA512Constants::Digest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.digest.should == SHA512Constants::BlankDigest + end + +end + +describe "Digest::SHA512.digest" do + + it "returns a digest" do + Digest::SHA512.digest(SHA512Constants::Contents).should == SHA512Constants::Digest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA512.digest(SHA512Constants::Contents).should == SHA512Constants::Digest + Digest::SHA512.digest("").should == SHA512Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha512/equal_spec.rb b/spec/ruby/library/digest/sha512/equal_spec.rb new file mode 100644 index 0000000000..5100ced6e8 --- /dev/null +++ b/spec/ruby/library/digest/sha512/equal_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#==" do + + it "equals itself" do + cur_digest = Digest::SHA512.new + cur_digest.should == cur_digest + end + + it "equals the string representing its hexdigest" do + cur_digest = Digest::SHA512.new + cur_digest.should == SHA512Constants::BlankHexdigest + end + + it "equals the appropriate object that responds to to_str" do + # blank digest + cur_digest = Digest::SHA512.new + (obj = mock(SHA512Constants::BlankHexdigest)).should_receive(:to_str).and_return(SHA512Constants::BlankHexdigest) + cur_digest.should == obj + + # non-blank digest + cur_digest = Digest::SHA512.new + cur_digest << "test" + d_value = cur_digest.hexdigest + (obj = mock(d_value)).should_receive(:to_str).and_return(d_value) + cur_digest.should == obj + end + + it "equals the same digest for a different object" do + cur_digest = Digest::SHA512.new + cur_digest2 = Digest::SHA512.new + cur_digest.should == cur_digest2 + end + +end diff --git a/spec/ruby/library/digest/sha512/file_spec.rb b/spec/ruby/library/digest/sha512/file_spec.rb new file mode 100644 index 0000000000..781ec781e5 --- /dev/null +++ b/spec/ruby/library/digest/sha512/file_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative '../../../core/file/shared/read' + +describe "Digest::SHA512.file" do + + describe "when passed a path to a file that exists" do + before :each do + @file = tmp("md5_temp") + touch(@file, 'wb') {|f| f.write SHA512Constants::Contents } + end + + after :each do + rm_r @file + end + + it "returns a Digest::SHA512 object" do + Digest::SHA512.file(@file).should be_kind_of(Digest::SHA512) + end + + it "returns a Digest::SHA512 object with the correct digest" do + Digest::SHA512.file(@file).digest.should == SHA512Constants::Digest + end + + it "calls #to_str on an object and returns the Digest::SHA512 with the result" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(@file) + result = Digest::SHA512.file(obj) + result.should be_kind_of(Digest::SHA512) + result.digest.should == SHA512Constants::Digest + end + end + + it_behaves_like :file_read_directory, :file, Digest::SHA512 + + it "raises a Errno::ENOENT when passed a path that does not exist" do + -> { Digest::SHA512.file("") }.should raise_error(Errno::ENOENT) + end + + it "raises a TypeError when passed nil" do + -> { Digest::SHA512.file(nil) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb b/spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb new file mode 100644 index 0000000000..e9b0da6191 --- /dev/null +++ b/spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#hexdigest!" do + + it "returns a hexdigest and resets the state" do + cur_digest = Digest::SHA512.new + + cur_digest << SHA512Constants::Contents + cur_digest.hexdigest!.should == SHA512Constants::Hexdigest + cur_digest.hexdigest.should == SHA512Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha512/hexdigest_spec.rb b/spec/ruby/library/digest/sha512/hexdigest_spec.rb new file mode 100644 index 0000000000..6e1dc3c642 --- /dev/null +++ b/spec/ruby/library/digest/sha512/hexdigest_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#hexdigest" do + + it "returns a hexdigest" do + cur_digest = Digest::SHA512.new + cur_digest.hexdigest.should == SHA512Constants::BlankHexdigest + + # add something to check that the state is reset later + cur_digest << "test" + + cur_digest.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + cur_digest.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest + + # after all is done, verify that the digest is in the original, blank state + cur_digest.hexdigest.should == SHA512Constants::BlankHexdigest + end + +end + +describe "Digest::SHA512.hexdigest" do + + it "returns a hexdigest" do + Digest::SHA512.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest + # second invocation is intentional, to make sure there are no side-effects + Digest::SHA512.hexdigest(SHA512Constants::Contents).should == SHA512Constants::Hexdigest + Digest::SHA512.hexdigest("").should == SHA512Constants::BlankHexdigest + end + +end diff --git a/spec/ruby/library/digest/sha512/inspect_spec.rb b/spec/ruby/library/digest/sha512/inspect_spec.rb new file mode 100644 index 0000000000..54a466043a --- /dev/null +++ b/spec/ruby/library/digest/sha512/inspect_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#inspect" do + + it "returns a Ruby object representation" do + cur_digest = Digest::SHA512.new + cur_digest.inspect.should == "#<#{SHA512Constants::Klass}: #{cur_digest.hexdigest()}>" + end + +end diff --git a/spec/ruby/library/digest/sha512/length_spec.rb b/spec/ruby/library/digest/sha512/length_spec.rb new file mode 100644 index 0000000000..e9fde90577 --- /dev/null +++ b/spec/ruby/library/digest/sha512/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::SHA512#length" do + it_behaves_like :sha512_length, :length +end diff --git a/spec/ruby/library/digest/sha512/reset_spec.rb b/spec/ruby/library/digest/sha512/reset_spec.rb new file mode 100644 index 0000000000..24a936d4ba --- /dev/null +++ b/spec/ruby/library/digest/sha512/reset_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#reset" do + + it "returns digest state to initial conditions" do + cur_digest = Digest::SHA512.new + cur_digest.update SHA512Constants::Contents + cur_digest.digest().should_not == SHA512Constants::BlankDigest + cur_digest.reset + cur_digest.digest().should == SHA512Constants::BlankDigest + end + +end diff --git a/spec/ruby/library/digest/sha512/shared/constants.rb b/spec/ruby/library/digest/sha512/shared/constants.rb new file mode 100644 index 0000000000..91787381ee --- /dev/null +++ b/spec/ruby/library/digest/sha512/shared/constants.rb @@ -0,0 +1,18 @@ +# encoding: binary + +require 'digest/sha2' + +module SHA512Constants + + Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + + Klass = ::Digest::SHA512 + BlockLength = 128 + DigestLength = 64 + BlankDigest = "\317\203\3415~\357\270\275\361T(P\326m\200\a\326 \344\005\vW\025\334\203\364\251!\323l\351\316G\320\321<]\205\362\260\377\203\030\322\207~\354/c\2711\275GAz\201\24582z\371'\332>" + Digest = "\241\231\232\365\002z\241\331\242\310=\367F\272\004\326\331g\315n\251Q\222\250\374E\257\254=\325\225\003SM\350\244\234\220\233=\031\230A;\000\203\233\340\323t\333\271\222w\266\307\2678\344\255j\003\216\300" + BlankHexdigest = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + Hexdigest = "a1999af5027aa1d9a2c83df746ba04d6d967cd6ea95192a8fc45afac3dd59503534de8a49c909b3d1998413b00839be0d374dbb99277b6c7b738e4ad6a038ec0" + Base64digest = "oZma9QJ6odmiyD33RroE1tlnzW6pUZKo/EWvrD3VlQNTTeiknJCbPRmYQTsAg5vg03TbuZJ3tse3OOStagOOwA==" + +end diff --git a/spec/ruby/library/digest/sha512/shared/length.rb b/spec/ruby/library/digest/sha512/shared/length.rb new file mode 100644 index 0000000000..c0609d5386 --- /dev/null +++ b/spec/ruby/library/digest/sha512/shared/length.rb @@ -0,0 +1,8 @@ +describe :sha512_length, shared: true do + it "returns the length of the digest" do + cur_digest = Digest::SHA512.new + cur_digest.send(@method).should == SHA512Constants::BlankDigest.size + cur_digest << SHA512Constants::Contents + cur_digest.send(@method).should == SHA512Constants::Digest.size + end +end diff --git a/spec/ruby/library/digest/sha512/shared/update.rb b/spec/ruby/library/digest/sha512/shared/update.rb new file mode 100644 index 0000000000..ca74dbf4df --- /dev/null +++ b/spec/ruby/library/digest/sha512/shared/update.rb @@ -0,0 +1,7 @@ +describe :sha512_update, shared: true do + it "can update" do + cur_digest = Digest::SHA512.new + cur_digest.send @method, SHA512Constants::Contents + cur_digest.digest.should == SHA512Constants::Digest + end +end diff --git a/spec/ruby/library/digest/sha512/size_spec.rb b/spec/ruby/library/digest/sha512/size_spec.rb new file mode 100644 index 0000000000..6d0acdabdb --- /dev/null +++ b/spec/ruby/library/digest/sha512/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/length' + +describe "Digest::SHA512#size" do + it_behaves_like :sha512_length, :size +end diff --git a/spec/ruby/library/digest/sha512/to_s_spec.rb b/spec/ruby/library/digest/sha512/to_s_spec.rb new file mode 100644 index 0000000000..68d86241c9 --- /dev/null +++ b/spec/ruby/library/digest/sha512/to_s_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' + +describe "Digest::SHA512#to_s" do + + it "returns a hexdigest" do + cur_digest = Digest::SHA512.new + cur_digest.to_s.should == SHA512Constants::BlankHexdigest + end + + it "does not change the internal state" do + cur_digest = Digest::SHA512.new + cur_digest.to_s.should == SHA512Constants::BlankHexdigest + cur_digest.to_s.should == SHA512Constants::BlankHexdigest + + cur_digest << SHA512Constants::Contents + cur_digest.to_s.should == SHA512Constants::Hexdigest + cur_digest.to_s.should == SHA512Constants::Hexdigest + end + +end diff --git a/spec/ruby/library/digest/sha512/update_spec.rb b/spec/ruby/library/digest/sha512/update_spec.rb new file mode 100644 index 0000000000..682d3a19bb --- /dev/null +++ b/spec/ruby/library/digest/sha512/update_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative 'shared/constants' +require_relative 'shared/update' + +describe "Digest::SHA512#update" do + it_behaves_like :sha512_update, :update +end diff --git a/spec/ruby/library/drb/fixtures/test_server.rb b/spec/ruby/library/drb/fixtures/test_server.rb new file mode 100644 index 0000000000..9d412f4ac7 --- /dev/null +++ b/spec/ruby/library/drb/fixtures/test_server.rb @@ -0,0 +1,8 @@ +class TestServer + def add(*args) + args.inject {|n,v| n+v} + end + def add_yield(x) + return (yield x)+1 + end +end diff --git a/spec/ruby/library/drb/start_service_spec.rb b/spec/ruby/library/drb/start_service_spec.rb new file mode 100644 index 0000000000..57a8cf6e15 --- /dev/null +++ b/spec/ruby/library/drb/start_service_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' + +# This does not work yet when run in CRuby via make test-spec: +# Gem::MissingSpecError: Could not find 'ruby2_keywords' (>= 0) among 28 total gem(s) +guard_not -> { MSpecScript.instance_variable_defined?(:@testing_ruby) } do + require_relative 'fixtures/test_server' + require 'drb' + + describe "DRb.start_service" do + before :each do + @server = DRb.start_service("druby://localhost:0", TestServer.new) + end + + after :each do + DRb.stop_service if @server + end + + it "runs a basic remote call" do + DRb.current_server.should == @server + obj = DRbObject.new(nil, @server.uri) + obj.add(1,2,3).should == 6 + end + + it "runs a basic remote call passing a block" do + DRb.current_server.should == @server + obj = DRbObject.new(nil, @server.uri) + obj.add_yield(2) do |i| + i.should == 2 + i+1 + end.should == 4 + end + end +end diff --git a/spec/ruby/library/erb/def_class_spec.rb b/spec/ruby/library/erb/def_class_spec.rb new file mode 100644 index 0000000000..fb687531e0 --- /dev/null +++ b/spec/ruby/library/erb/def_class_spec.rb @@ -0,0 +1,31 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#def_class" do + + it "return an unnamed class which has instance method to render eRuby script" do + input = <<'END' +@arg1=<%=@arg1.inspect%> +@arg2=<%=@arg2.inspect%> +END + expected = <<'END' +@arg1="foo" +@arg2=123 +END + class MyClass1ForErb_ + def initialize(arg1, arg2) + @arg1 = arg1; @arg2 = arg2 + end + end + filename = 'example.rhtml' + #erb = ERB.new(File.read(filename)) + erb = ERB.new(input) + erb.filename = filename + MyClass1ForErb = erb.def_class(MyClass1ForErb_, 'render()') + MyClass1ForErb.method_defined?(:render).should == true + MyClass1ForErb.new('foo', 123).render().should == expected + ensure + Object.send(:remove_const, :MyClass1ForErb) + end + +end diff --git a/spec/ruby/library/erb/def_method_spec.rb b/spec/ruby/library/erb/def_method_spec.rb new file mode 100644 index 0000000000..188789a693 --- /dev/null +++ b/spec/ruby/library/erb/def_method_spec.rb @@ -0,0 +1,26 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#def_method" do + + it "define module's instance method to render eRuby file" do + input = <<'END' +arg1=<%= arg1.inspect %> +arg2=<%= arg2.inspect %> +END + expected = <<'END' +arg1="foo" +arg2=123 +END + # + filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml + #erb = ERB.new(File.read(filename)) + erb = ERB.new(input) + class MyClass0ForErb + end + erb.def_method(MyClass0ForErb, 'render(arg1, arg2)', filename) + MyClass0ForErb.method_defined?(:render) + MyClass0ForErb.new.render('foo', 123).should == expected + end + +end diff --git a/spec/ruby/library/erb/def_module_spec.rb b/spec/ruby/library/erb/def_module_spec.rb new file mode 100644 index 0000000000..5f67aeb2b9 --- /dev/null +++ b/spec/ruby/library/erb/def_module_spec.rb @@ -0,0 +1,30 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#def_module" do + + it "return unnamed module which has instance method to render eRuby" do + input = <<'END' +arg1=<%= arg1.inspect %> +arg2=<%= arg2.inspect %> +END + expected = <<'END' +arg1="foo" +arg2=123 +END + filename = 'example.rhtml' + #erb = ERB.new(File.read(filename)) + erb = ERB.new(input) + erb.filename = filename + MyModule2ForErb = erb.def_module('render(arg1, arg2)') + MyModule2ForErb.method_defined?(':render') + class MyClass2ForErb + include MyModule2ForErb + end + MyClass2ForErb.new.render('foo', 123).should == expected + ensure + Object.send(:remove_const, :MyClass2ForErb) + Object.send(:remove_const, :MyModule2ForErb) + end + +end diff --git a/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb b/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb new file mode 100644 index 0000000000..1cd7582936 --- /dev/null +++ b/spec/ruby/library/erb/defmethod/def_erb_method_spec.rb @@ -0,0 +1,66 @@ +require 'erb' +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe "ERB::DefMethod.def_erb_method" do + + + input = <<'END' +<% for item in @items %> +<b><%= item %></b> +<% end %> +END + + + it "define method to render eRuby file as an instance method of current module" do + expected = <<'END' + +<b>10</b> + +<b>20</b> + +<b>30</b> + +END + # + begin + file = tmp('_example.rhtml') + File.open(file, 'w') {|f| f.write(input) } + klass = Class.new do + extend ERB::DefMethod + def_erb_method('render()', file) + def initialize(items) + @items = items + end + end + klass.new([10,20,30]).render().should == expected + ensure + rm_r file + end + + end + + + it "define method to render eRuby object as an instance method of current module" do + expected = <<'END' +<b>10</b> +<b>20</b> +<b>30</b> +END + # + MY_INPUT4_FOR_ERB = input + class MyClass4ForErb + extend ERB::DefMethod + erb = ERBSpecs.new_erb(MY_INPUT4_FOR_ERB, trim_mode: '<>') + def_erb_method('render()', erb) + def initialize(items) + @items = items + end + end + MyClass4ForErb.new([10,20,30]).render().should == expected + ensure + Object.send(:remove_const, :MY_INPUT4_FOR_ERB) + end + + +end diff --git a/spec/ruby/library/erb/filename_spec.rb b/spec/ruby/library/erb/filename_spec.rb new file mode 100644 index 0000000000..8ecaed7343 --- /dev/null +++ b/spec/ruby/library/erb/filename_spec.rb @@ -0,0 +1,40 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#filename" do + it "raises an exception if there are errors processing content" do + filename = 'foobar.rhtml' + erb = ERB.new('<% if true %>') # will raise SyntaxError + erb.filename = filename + -> { + begin + erb.result(binding) + rescue Exception => e + @ex = e + raise e + end + }.should raise_error(SyntaxError) + expected = filename + + @ex.message =~ /^(.*?):(\d+): / + $1.should == expected + $2.to_i.should == 1 + end + + it "uses '(erb)' as filename when filename is not set" do + erb = ERB.new('<% if true %>') # will raise SyntaxError + -> { + begin + erb.result(binding) + rescue Exception => e + @ex = e + raise e + end + }.should raise_error(SyntaxError) + expected = '(erb)' + + @ex.message =~ /^(.*?):(\d+): / + $1.should == expected + $2.to_i.should == 1 + end +end diff --git a/spec/ruby/library/erb/fixtures/classes.rb b/spec/ruby/library/erb/fixtures/classes.rb new file mode 100644 index 0000000000..e07a6ed68d --- /dev/null +++ b/spec/ruby/library/erb/fixtures/classes.rb @@ -0,0 +1,5 @@ +module ERBSpecs + def self.new_erb(input, trim_mode: nil) + ERB.new(input, trim_mode: trim_mode) + end +end diff --git a/spec/ruby/library/erb/new_spec.rb b/spec/ruby/library/erb/new_spec.rb new file mode 100644 index 0000000000..f8192bff99 --- /dev/null +++ b/spec/ruby/library/erb/new_spec.rb @@ -0,0 +1,157 @@ +require 'erb' +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "ERB.new" do + before :all do + @eruby_str = <<'END' +<ul> +<% list = [1,2,3] %> +<% for item in list %> +<% if item %> +<li><%= item %></li> +<% end %> +<% end %> +</ul> +END + + @eruby_str2 = <<'END' +<ul> +% list = [1,2,3] +%for item in list +% if item + <li><%= item %> + <% end %> +<% end %> +</ul> +%%% +END + + end + + it "compiles eRuby script into ruby code when trim mode is 0 or not specified" do + expected = "<ul>\n\n\n\n<li>1</li>\n\n\n\n<li>2</li>\n\n\n\n<li>3</li>\n\n\n</ul>\n" + [0, nil].each do |trim_mode| + ERBSpecs.new_erb(@eruby_str, trim_mode: trim_mode).result.should == expected + end + end + + it "warns invalid trim_mode" do + -> do + ERBSpecs.new_erb(@eruby_str, trim_mode: '') + end.should complain(/Invalid ERB trim mode/) + end + + it "removes '\n' when trim_mode is 1 or '>'" do + expected = "<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n</ul>\n" + [1, '>'].each do |trim_mode| + ERBSpecs.new_erb(@eruby_str, trim_mode: trim_mode).result.should == expected + end + end + + it "removes spaces at beginning of line and '\n' when trim_mode is 2 or '<>'" do + expected = "<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n</ul>\n" + [2, '<>'].each do |trim_mode| + ERBSpecs.new_erb(@eruby_str, trim_mode: trim_mode).result.should == expected + end + end + + it "removes spaces around '<%- -%>' when trim_mode is '-'" do + expected = "<ul>\n <li>1 <li>2 <li>3</ul>\n" + input = <<'END' +<ul> +<%- for item in [1,2,3] -%> + <%- if item -%> + <li><%= item -%> + <%- end -%> +<%- end -%> +</ul> +END + + ERBSpecs.new_erb(input, trim_mode: '-').result.should == expected + end + + + it "does not support '<%-= expr %> even when trim_mode is '-'" do + + input = <<'END' +<p> + <%= expr -%> + <%-= expr -%> +</p> +END + + -> { + ERBSpecs.new_erb(input, trim_mode: '-').result + }.should raise_error(SyntaxError) + end + + it "regards lines starting with '%' as '<% ... %>' when trim_mode is '%'" do + expected = "<ul>\n <li>1\n \n <li>2\n \n <li>3\n \n\n</ul>\n%%\n" + ERBSpecs.new_erb(@eruby_str2, trim_mode: "%").result.should == expected + end + it "regards lines starting with '%' as '<% ... %>' and remove \"\\n\" when trim_mode is '%>'" do + expected = "<ul>\n <li>1 <li>2 <li>3 </ul>\n%%\n" + ERBSpecs.new_erb(@eruby_str2, trim_mode: '%>').result.should == expected + end + + + it "regard lines starting with '%' as '<% ... %>' and remove \"\\n\" when trim_mode is '%<>'" do + expected = "<ul>\n <li>1\n \n <li>2\n \n <li>3\n \n</ul>\n%%\n" + ERBSpecs.new_erb(@eruby_str2, trim_mode: '%<>').result.should == expected + end + + + it "regard lines starting with '%' as '<% ... %>' and spaces around '<%- -%>' when trim_mode is '%-'" do + expected = "<ul>\n<li>1</li>\n<li>2</li>\n</ul>\n%%\n" + input = <<'END' +<ul> +%list = [1,2] +%for item in list +<li><%= item %></li> +<% end %></ul> +%%% +END + + ERBSpecs.new_erb(input, trim_mode: '%-').result.should == expected + end + + it "changes '_erbout' variable name in the produced source" do + input = @eruby_str + match_erbout = ERB.new(input, trim_mode: nil).src + match_buf = ERB.new(input, trim_mode: nil, eoutvar: 'buf').src + match_erbout.gsub("_erbout", "buf").should == match_buf + end + + + it "ignores '<%# ... %>'" do + input = <<'END' +<%# for item in list %> +<b><%#= item %></b> +<%# end %> +END + ERBSpecs.new_erb(input).result.should == "\n<b></b>\n\n" + ERBSpecs.new_erb(input, trim_mode: '<>').result.should == "<b></b>\n" + end + + it "forget local variables defined previous one" do + ERB.new(@eruby_str).result + ->{ ERB.new("<%= list %>").result }.should raise_error(NameError) + end + + version_is ERB.const_get(:VERSION, false), ""..."6.0.0" do + describe "warning about arguments" do + it "warns when passed safe_level and later arguments" do + -> { + ERB.new(@eruby_str, nil, '%') + }.should complain(/warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments./) + end + + it "does not warn when passed arguments as keyword argument" do + -> { + ERB.new(@eruby_str, trim_mode: '%') + }.should_not complain(/warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments./) + end + end + end +end diff --git a/spec/ruby/library/erb/result_spec.rb b/spec/ruby/library/erb/result_spec.rb new file mode 100644 index 0000000000..a29c1ccedb --- /dev/null +++ b/spec/ruby/library/erb/result_spec.rb @@ -0,0 +1,86 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#result" do + + + it "return the result of compiled ruby code" do + input = <<'END' +<ul> +<% for item in list %> + <li><%= item %> +<% end %> +</ul> +END + expected = <<'END' +<ul> + + <li>AAA + + <li>BBB + + <li>CCC + +</ul> +END + erb = ERB.new(input) + list = %w[AAA BBB CCC] + actual = erb.result(binding) + actual.should == expected + end + + + it "share local variables" do + input = "<% var = 456 %>" + expected = 456 + var = 123 + ERB.new(input).result(binding) + var.should == expected + end + + + it "is not able to h() or u() unless including ERB::Util" do + input = "<%=h '<>' %>" + -> { + ERB.new(input).result() + }.should raise_error(NameError) + end + + + it "is able to h() or u() if ERB::Util is included" do + myerb1 = Class.new do + include ERB::Util + def main + input = "<%=h '<>' %>" + return ERB.new(input).result(binding) + end + end + expected = '<>' + actual = myerb1.new.main() + actual.should == expected + end + + + it "use TOPLEVEL_BINDING if binding is not passed" do + myerb2 = Class.new do + include ERB::Util + def main1 + #input = "<%= binding.to_s %>" + input = "<%= _xxx_var_ %>" + return ERB.new(input).result() + end + def main2 + input = "<%=h '<>' %>" + return ERB.new(input).result() + end + end + + eval '_xxx_var_ = 123', TOPLEVEL_BINDING + expected = '123' + myerb2.new.main1().should == expected + + -> { + myerb2.new.main2() + }.should raise_error(NameError) + end +end diff --git a/spec/ruby/library/erb/run_spec.rb b/spec/ruby/library/erb/run_spec.rb new file mode 100644 index 0000000000..602e53ab38 --- /dev/null +++ b/spec/ruby/library/erb/run_spec.rb @@ -0,0 +1,96 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#run" do + # TODO: what is this? why does it not use + # lambda { ... }.should output + def _steal_stdout + orig = $stdout + s = +'' + def s.write(arg); self << arg.to_s; end + $stdout = s + begin + yield + ensure + $stdout = orig + end + return s + end + + it "print the result of compiled ruby code" do + input = <<END +<ul> +<% for item in list %> + <li><%= item %> +<% end %> +</ul> +END + expected = <<END +<ul> + + <li>AAA + + <li>BBB + + <li>CCC + +</ul> +END + erb = ERB.new(input) + list = %w[AAA BBB CCC] + actual = _steal_stdout { erb.run(binding) } + actual.should == expected + end + + it "share local variables" do + input = "<% var = 456 %>" + expected = 456 + var = 123 + _steal_stdout { ERB.new(input).run(binding) } + var.should == expected + end + + it "is not able to h() or u() unless including ERB::Util" do + input = "<%=h '<>' %>" + -> { + _steal_stdout { ERB.new(input).run() } + }.should raise_error(NameError) + end + + it "is able to h() or u() if ERB::Util is included" do + myerb1 = Class.new do + include ERB::Util + def main + input = "<%=h '<>' %>" + ERB.new(input).run(binding) + end + end + expected = '<>' + actual = _steal_stdout { myerb1.new.main() } + actual.should == expected + end + + it "use TOPLEVEL_BINDING if binding is not passed" do + myerb2 = Class.new do + include ERB::Util + def main1 + #input = "<%= binding.to_s %>" + input = "<%= _xxx_var_ %>" + return ERB.new(input).run() + end + def main2 + input = "<%=h '<>' %>" + return ERB.new(input).run() + end + end + + eval '_xxx_var_ = 123', TOPLEVEL_BINDING + expected = '123' + actual = _steal_stdout { myerb2.new.main1() } + actual.should == expected + + -> { + _steal_stdout { myerb2.new.main2() } + }.should raise_error(NameError) + end +end diff --git a/spec/ruby/library/erb/src_spec.rb b/spec/ruby/library/erb/src_spec.rb new file mode 100644 index 0000000000..fc11b7e4b7 --- /dev/null +++ b/spec/ruby/library/erb/src_spec.rb @@ -0,0 +1,33 @@ +require 'erb' +require_relative '../../spec_helper' + +describe "ERB#src" do + + it "returns the compiled ruby code evaluated to a String" do + # note that what concrete code is emitted is not guaranteed. + + input = <<'END' +<ul> +<% for item in list %> + <li><%= item %> +<% end %> +</ul> +END + + expected = <<'END' +<ul> + + <li>AAA + + <li>BBB + + <li>CCC + +</ul> +END + + list = %w[AAA BBB CCC] + eval(ERB.new(input).src).should == expected + end + +end diff --git a/spec/ruby/library/erb/util/h_spec.rb b/spec/ruby/library/erb/util/h_spec.rb new file mode 100644 index 0000000000..6de79cfd92 --- /dev/null +++ b/spec/ruby/library/erb/util/h_spec.rb @@ -0,0 +1,7 @@ +require 'erb' +require_relative '../../../spec_helper' +require_relative 'shared/html_escape' + +describe "ERB::Util.h" do + it_behaves_like :erb_util_html_escape, :h +end diff --git a/spec/ruby/library/erb/util/html_escape_spec.rb b/spec/ruby/library/erb/util/html_escape_spec.rb new file mode 100644 index 0000000000..1c15fb8791 --- /dev/null +++ b/spec/ruby/library/erb/util/html_escape_spec.rb @@ -0,0 +1,7 @@ +require 'erb' +require_relative '../../../spec_helper' +require_relative 'shared/html_escape' + +describe "ERB::Util.html_escape" do + it_behaves_like :erb_util_html_escape, :html_escape +end diff --git a/spec/ruby/library/erb/util/shared/html_escape.rb b/spec/ruby/library/erb/util/shared/html_escape.rb new file mode 100644 index 0000000000..71b378755e --- /dev/null +++ b/spec/ruby/library/erb/util/shared/html_escape.rb @@ -0,0 +1,42 @@ +describe :erb_util_html_escape, shared: true do + it "escape (& < > \" ') to (& < > " ')" do + input = '& < > " \'' + expected = '& < > " '' + ERB::Util.__send__(@method, input).should == expected + end + + it "not escape characters except (& < > \" ')" do + input = (0x20..0x7E).to_a.collect {|ch| ch.chr}.join('') + expected = input. + gsub(/&/,'&'). + gsub(/</,'<'). + gsub(/>/,'>'). + gsub(/'/,'''). + gsub(/"/,'"') + ERB::Util.__send__(@method, input).should == expected + end + + it "return empty string when argument is nil" do + input = nil + expected = '' + ERB::Util.__send__(@method, input).should == expected + end + + it "returns string when argument is number" do + input = 123 + expected = '123' + ERB::Util.__send__(@method, input).should == expected + input = 3.14159 + expected = '3.14159' + ERB::Util.__send__(@method, input).should == expected + end + + it "returns string when argument is boolean" do + input = true + expected = 'true' + ERB::Util.__send__(@method, input).should == expected + input = false + expected = 'false' + ERB::Util.__send__(@method, input).should == expected + end +end diff --git a/spec/ruby/library/erb/util/shared/url_encode.rb b/spec/ruby/library/erb/util/shared/url_encode.rb new file mode 100644 index 0000000000..34009c4903 --- /dev/null +++ b/spec/ruby/library/erb/util/shared/url_encode.rb @@ -0,0 +1,42 @@ +describe :erb_util_url_encode, shared: true do + it "encode characters" do + #input = (0x20..0x7E).to_a.collect{|ch| ch.chr}.join + input = " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}" + expected = "%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D" + ERB::Util.__send__(@method, input).should == expected + end + + it "does not escape tilde" do + ERB::Util.__send__(@method, "~").should == "~" + end + + it "encode unicode string" do + input = "https://ja.wikipedia.org/wiki/\343\203\255\343\203\240\343\202\271\343\202\253\343\203\273\343\203\221\343\203\255\343\203\273\343\202\246\343\203\253\343\203\273\343\203\251\343\203\224\343\203\245\343\202\277" + expected = 'https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%E3%83%AD%E3%83%A0%E3%82%B9%E3%82%AB%E3%83%BB%E3%83%91%E3%83%AD%E3%83%BB%E3%82%A6%E3%83%AB%E3%83%BB%E3%83%A9%E3%83%94%E3%83%A5%E3%82%BF' + ERB::Util.__send__(@method, input).should == expected + end + + it "returns empty string when argument is nil" do + input = nil + expected = '' + ERB::Util.__send__(@method, input).should == expected + end + + it "returns string when argument is number" do + input = 123 + expected = '123' + ERB::Util.__send__(@method, input).should == expected + input = 3.14159 + expected = '3.14159' + ERB::Util.__send__(@method, input).should == expected + end + + it "returns string when argument is boolean" do + input = true + expected = 'true' + ERB::Util.__send__(@method, input).should == expected + input = false + expected = 'false' + ERB::Util.__send__(@method, input).should == expected + end +end diff --git a/spec/ruby/library/erb/util/u_spec.rb b/spec/ruby/library/erb/util/u_spec.rb new file mode 100644 index 0000000000..2a08451031 --- /dev/null +++ b/spec/ruby/library/erb/util/u_spec.rb @@ -0,0 +1,7 @@ +require 'erb' +require_relative '../../../spec_helper' +require_relative 'shared/url_encode' + +describe "ERB::Util.u" do + it_behaves_like :erb_util_url_encode, :u +end diff --git a/spec/ruby/library/erb/util/url_encode_spec.rb b/spec/ruby/library/erb/util/url_encode_spec.rb new file mode 100644 index 0000000000..5569a1cc64 --- /dev/null +++ b/spec/ruby/library/erb/util/url_encode_spec.rb @@ -0,0 +1,7 @@ +require 'erb' +require_relative '../../../spec_helper' +require_relative 'shared/url_encode' + +describe "ERB::Util.url_encode" do + it_behaves_like :erb_util_url_encode, :url_encode +end diff --git a/spec/ruby/library/etc/confstr_spec.rb b/spec/ruby/library/etc/confstr_spec.rb new file mode 100644 index 0000000000..5b43461150 --- /dev/null +++ b/spec/ruby/library/etc/confstr_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is_not :windows, :android do + describe "Etc.confstr" do + it "returns a String for Etc::CS_PATH" do + Etc.confstr(Etc::CS_PATH).should be_an_instance_of(String) + end + + it "raises Errno::EINVAL for unknown configuration variables" do + -> { Etc.confstr(-1) }.should raise_error(Errno::EINVAL) + end + end +end diff --git a/spec/ruby/library/etc/endgrent_spec.rb b/spec/ruby/library/etc/endgrent_spec.rb new file mode 100644 index 0000000000..88de231d87 --- /dev/null +++ b/spec/ruby/library/etc/endgrent_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/windows' +require 'etc' + +describe "Etc.endgrent" do + it_behaves_like :etc_on_windows, :endgrent +end diff --git a/spec/ruby/library/etc/endpwent_spec.rb b/spec/ruby/library/etc/endpwent_spec.rb new file mode 100644 index 0000000000..e4e564d251 --- /dev/null +++ b/spec/ruby/library/etc/endpwent_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/windows' +require 'etc' + +describe "Etc.endpwent" do + it_behaves_like :etc_on_windows, :endpwent +end diff --git a/spec/ruby/library/etc/getgrent_spec.rb b/spec/ruby/library/etc/getgrent_spec.rb new file mode 100644 index 0000000000..45a4442262 --- /dev/null +++ b/spec/ruby/library/etc/getgrent_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/windows' +require 'etc' + +describe "Etc.getgrent" do + it_behaves_like :etc_on_windows, :getgrent +end diff --git a/spec/ruby/library/etc/getgrgid_spec.rb b/spec/ruby/library/etc/getgrgid_spec.rb new file mode 100644 index 0000000000..14da5e041d --- /dev/null +++ b/spec/ruby/library/etc/getgrgid_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is :windows do + describe "Etc.getgrgid" do + it "returns nil" do + Etc.getgrgid(1).should == nil + Etc.getgrgid(nil).should == nil + Etc.getgrgid('nil').should == nil + end + end +end + +# TODO: verify these on non-windows, non-darwin OS +platform_is_not :windows do + grpname = nil + guard -> { + grpname = IO.popen(%w'id -gn', err: IO::NULL, &:read).chomp + $?.success? + } do + describe "Etc.getgrgid" do + before :all do + @gid = `id -g`.strip.to_i + @name = grpname + end + + it "returns a Etc::Group struct instance for the given user" do + gr = Etc.getgrgid(@gid) + + gr.is_a?(Etc::Group).should == true + gr.gid.should == @gid + gr.name.should == @name + end + + it "returns the Etc::Group for a given gid if it exists" do + grp = Etc.getgrgid(@gid) + grp.should be_kind_of(Etc::Group) + grp.gid.should == @gid + grp.name.should == @name + end + + it "uses Process.gid as the default value for the argument" do + gr = Etc.getgrgid + + gr.gid.should == @gid + gr.name.should == @name + end + + it "raises if the group does not exist" do + -> { Etc.getgrgid(9876)}.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed an Integer" do + -> { Etc.getgrgid("foo") }.should raise_error(TypeError) + -> { Etc.getgrgid(nil) }.should raise_error(TypeError) + end + + it "can be called safely by multiple threads" do + 20.times.map do + Thread.new do + 100.times do + Etc.getgrgid(@gid).gid.should == @gid + end + end + end.each(&:join) + end + end + end +end diff --git a/spec/ruby/library/etc/getgrnam_spec.rb b/spec/ruby/library/etc/getgrnam_spec.rb new file mode 100644 index 0000000000..fa49f15349 --- /dev/null +++ b/spec/ruby/library/etc/getgrnam_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is :windows do + describe "Etc.getgrnam" do + it "returns nil" do + Etc.getgrnam(1).should == nil + Etc.getgrnam(nil).should == nil + Etc.getgrnam('nil').should == nil + end + end +end + +platform_is_not :windows, :android do + describe "Etc.getgrnam" do + it "returns a Etc::Group struct instance for the given group" do + gr_name = Etc.getgrent.name + Etc.endgrent + gr = Etc.getgrnam(gr_name) + gr.is_a?(Etc::Group).should == true + end + + it "only accepts strings as argument" do + -> { + Etc.getgrnam(123) + Etc.getgrnam(nil) + }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/etc/getlogin_spec.rb b/spec/ruby/library/etc/getlogin_spec.rb new file mode 100644 index 0000000000..7a4fd79ae2 --- /dev/null +++ b/spec/ruby/library/etc/getlogin_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc.getlogin" do + it "returns the name associated with the current login activity" do + getlogin_null = false + + # POSIX logname(1) shows getlogin(2)'s result + # NOTE: Etc.getlogin returns ENV['USER'] if getlogin(2) returns NULL + begin + # make Etc.getlogin to return nil if getlogin(3) returns NULL + envuser, ENV['USER'] = ENV['USER'], nil + if Etc.getlogin + if ENV['TRAVIS'] and platform_is(:darwin) + # See https://travis-ci.org/ruby/spec/jobs/285967744 + # and https://travis-ci.org/ruby/spec/jobs/285999602 + Etc.getlogin.should be_an_instance_of(String) + else + # Etc.getlogin returns the same result of logname(2) + # if it returns non NULL + if system("which logname", out: File::NULL, err: File::NULL) + Etc.getlogin.should == `logname`.chomp + else + # fallback to `id` command since `logname` is not available + Etc.getlogin.should == `id -un`.chomp + end + end + else + # Etc.getlogin may return nil if the login name is not set + # because of chroot or sudo or something. + Etc.getlogin.should be_nil + getlogin_null = true + end + ensure + ENV['USER'] = envuser + end + + # if getlogin(2) returns NULL, Etc.getlogin returns ENV['USER'] + if getlogin_null + Etc.getlogin.should == ENV['USER'] + end + end +end diff --git a/spec/ruby/library/etc/getpwent_spec.rb b/spec/ruby/library/etc/getpwent_spec.rb new file mode 100644 index 0000000000..9a911aed8f --- /dev/null +++ b/spec/ruby/library/etc/getpwent_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/windows' +require 'etc' + +describe "Etc.getpwent" do + it_behaves_like :etc_on_windows, :getpwent +end diff --git a/spec/ruby/library/etc/getpwnam_spec.rb b/spec/ruby/library/etc/getpwnam_spec.rb new file mode 100644 index 0000000000..3f4416aa9d --- /dev/null +++ b/spec/ruby/library/etc/getpwnam_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is :windows do + describe "Etc.getpwnam" do + it "returns nil" do + Etc.getpwnam(1).should == nil + Etc.getpwnam(nil).should == nil + Etc.getpwnam('nil').should == nil + end + end +end + +platform_is_not :windows do + describe "Etc.getpwnam" do + it "returns a Etc::Passwd struct instance for the given user" do + pw = Etc.getpwnam(`whoami`.strip) + pw.is_a?(Etc::Passwd).should == true + end + + it "only accepts strings as argument" do + -> { + Etc.getpwnam(123) + Etc.getpwnam(nil) + }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/etc/getpwuid_spec.rb b/spec/ruby/library/etc/getpwuid_spec.rb new file mode 100644 index 0000000000..5b98f0f8d9 --- /dev/null +++ b/spec/ruby/library/etc/getpwuid_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is :windows do + describe "Etc.getpwuid" do + it "returns nil" do + Etc.getpwuid(1).should == nil + Etc.getpwuid(nil).should == nil + Etc.getpwuid('nil').should == nil + end + end +end + +platform_is_not :windows do + describe "Etc.getpwuid" do + before :all do + @pw = Etc.getpwuid(`id -u`.strip.to_i) + end + + it "returns a Etc::Passwd struct instance for the given user" do + @pw.is_a?(Etc::Passwd).should == true + end + + it "uses Process.uid as the default value for the argument" do + pw = Etc.getpwuid + pw.should == @pw + end + + it "only accepts integers as argument" do + -> { + Etc.getpwuid("foo") + Etc.getpwuid(nil) + }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/etc/group_spec.rb b/spec/ruby/library/etc/group_spec.rb new file mode 100644 index 0000000000..fda808eec9 --- /dev/null +++ b/spec/ruby/library/etc/group_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require_relative 'shared/windows' +require 'etc' + +describe "Etc.group" do + it_behaves_like :etc_on_windows, :group + + platform_is_not :windows, :android do + it "returns a Etc::Group struct" do + group = Etc.group + begin + group.should be_an_instance_of(Etc::Group) + ensure + Etc.endgrent + end + end + + it "raises a RuntimeError for parallel iteration" do + proc { + Etc.group do | group | + Etc.group do | group2 | + end + end + }.should raise_error(RuntimeError) + end + end +end diff --git a/spec/ruby/library/etc/nprocessors_spec.rb b/spec/ruby/library/etc/nprocessors_spec.rb new file mode 100644 index 0000000000..ec7ffc81da --- /dev/null +++ b/spec/ruby/library/etc/nprocessors_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc.nprocessors" do + it "returns the number of online processors" do + Etc.nprocessors.should be_kind_of(Integer) + Etc.nprocessors.should >= 1 + end +end diff --git a/spec/ruby/library/etc/passwd_spec.rb b/spec/ruby/library/etc/passwd_spec.rb new file mode 100644 index 0000000000..7157fd3f2e --- /dev/null +++ b/spec/ruby/library/etc/passwd_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is_not :windows do + describe "Etc.passwd" do + it "returns a Etc::Passwd struct" do + passwd = Etc.passwd + begin + passwd.should be_an_instance_of(Etc::Passwd) + ensure + Etc.endpwent + end + end + end +end diff --git a/spec/ruby/library/etc/shared/windows.rb b/spec/ruby/library/etc/shared/windows.rb new file mode 100644 index 0000000000..8bae235199 --- /dev/null +++ b/spec/ruby/library/etc/shared/windows.rb @@ -0,0 +1,7 @@ +describe :etc_on_windows, shared: true do + platform_is :windows do + it "returns nil" do + Etc.send(@method).should == nil + end + end +end diff --git a/spec/ruby/library/etc/struct_group_spec.rb b/spec/ruby/library/etc/struct_group_spec.rb new file mode 100644 index 0000000000..b2147e306d --- /dev/null +++ b/spec/ruby/library/etc/struct_group_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc::Group" do + platform_is_not :windows do + grpname = IO.popen(%w'id -gn', err: IO::NULL, &:read) + next unless $?.success? + grpname.chomp! + + before :all do + @g = Etc.getgrgid(`id -g`.strip.to_i) + end + + it "returns group name" do + @g.name.should == grpname + end + + it "returns group password" do + @g.passwd.is_a?(String).should == true + end + + it "returns group id" do + @g.gid.should == `id -g`.strip.to_i + end + + it "returns an array of users belonging to the group" do + @g.mem.is_a?(Array).should == true + end + + it "can be compared to another object" do + (@g == nil).should == false + (@g == Object.new).should == false + end + end +end diff --git a/spec/ruby/library/etc/struct_passwd_spec.rb b/spec/ruby/library/etc/struct_passwd_spec.rb new file mode 100644 index 0000000000..dc37c17e7d --- /dev/null +++ b/spec/ruby/library/etc/struct_passwd_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc::Passwd" do + platform_is_not :windows do + before :all do + @pw = Etc.getpwuid(`id -u`.strip.to_i) + end + + it "returns user name" do + @pw.name.should == `id -un`.strip + end + + it "returns user password" do + @pw.passwd.is_a?(String).should == true + end + + it "returns user id" do + @pw.uid.should == `id -u`.strip.to_i + end + + it "returns user group id" do + @pw.gid.should == `id -g`.strip.to_i + end + + it "returns user personal information (gecos field)" do + @pw.gecos.is_a?(String).should == true + end + + it "returns user home directory" do + @pw.dir.is_a?(String).should == true + end + + it "returns user shell" do + @pw.shell.is_a?(String).should == true + end + + it "can be compared to another object" do + (@pw == nil).should == false + (@pw == Object.new).should == false + end + end +end diff --git a/spec/ruby/library/etc/sysconf_spec.rb b/spec/ruby/library/etc/sysconf_spec.rb new file mode 100644 index 0000000000..1f2c7a770f --- /dev/null +++ b/spec/ruby/library/etc/sysconf_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require 'etc' + +platform_is_not :windows do + describe "Etc.sysconf" do + %w[ + SC_ARG_MAX SC_CHILD_MAX SC_HOST_NAME_MAX SC_LOGIN_NAME_MAX SC_NGROUPS_MAX + SC_CLK_TCK SC_OPEN_MAX SC_PAGESIZE SC_RE_DUP_MAX SC_STREAM_MAX + SC_SYMLOOP_MAX SC_TTY_NAME_MAX SC_TZNAME_MAX SC_VERSION + ].each do |const| + it "returns the value of POSIX.1 system configuration variable #{const}" do + var = Etc.const_get(const) + value = Etc.sysconf(var) + if value.nil? + value.should == nil + else + value.should be_kind_of(Integer) + end + end + end + end +end diff --git a/spec/ruby/library/etc/sysconfdir_spec.rb b/spec/ruby/library/etc/sysconfdir_spec.rb new file mode 100644 index 0000000000..8538faa65b --- /dev/null +++ b/spec/ruby/library/etc/sysconfdir_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc.sysconfdir" do + it "returns a String" do + Etc.sysconfdir.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/etc/systmpdir_spec.rb b/spec/ruby/library/etc/systmpdir_spec.rb new file mode 100644 index 0000000000..5b007aa9f9 --- /dev/null +++ b/spec/ruby/library/etc/systmpdir_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc.systmpdir" do + it "returns a String" do + Etc.systmpdir.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/etc/uname_spec.rb b/spec/ruby/library/etc/uname_spec.rb new file mode 100644 index 0000000000..a42558f593 --- /dev/null +++ b/spec/ruby/library/etc/uname_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'etc' + +describe "Etc.uname" do + it "returns a Hash with the documented keys" do + uname = Etc.uname + uname.should be_kind_of(Hash) + uname.should.key?(:sysname) + uname.should.key?(:nodename) + uname.should.key?(:release) + uname.should.key?(:version) + uname.should.key?(:machine) + end +end diff --git a/spec/ruby/library/expect/expect_spec.rb b/spec/ruby/library/expect/expect_spec.rb new file mode 100644 index 0000000000..76ea3d5451 --- /dev/null +++ b/spec/ruby/library/expect/expect_spec.rb @@ -0,0 +1,63 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'expect' + + describe "IO#expect" do + before :each do + @read, @write = IO.pipe + end + + after :each do + @read.close unless @read.closed? + @write.close unless @write.closed? + end + + it "matches data against a Regexp" do + @write << "prompt> hello" + + result = @read.expect(/[pf]rompt>/) + result.should == ["prompt>"] + end + + it "matches data against a String" do + @write << "prompt> hello" + + result = @read.expect("prompt>") + result.should == ["prompt>"] + end + + it "returns any captures of the Regexp" do + @write << "prompt> hello" + + result = @read.expect(/(pro)mpt(>)/) + result.should == ["prompt>", "pro", ">"] + end + + it "returns raises IOError if the IO is closed" do + @write << "prompt> hello" + @read.close + + -> { + @read.expect("hello") + }.should raise_error(IOError) + end + + it "returns nil if eof is hit" do + @write << "pro" + @write.close + + @read.expect("prompt").should be_nil + end + + it "yields the result if a block is given" do + @write << "prompt> hello" + + res = nil + + @read.expect("prompt>") { |x| res = x } + + res.should == ["prompt>"] + end + end +end diff --git a/spec/ruby/library/fiddle/handle/initialize_spec.rb b/spec/ruby/library/fiddle/handle/initialize_spec.rb new file mode 100644 index 0000000000..51c2470efd --- /dev/null +++ b/spec/ruby/library/fiddle/handle/initialize_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'fiddle' + +describe "Fiddle::Handle#initialize" do + it "raises Fiddle::DLError if the library cannot be found" do + -> { + Fiddle::Handle.new("doesnotexist.doesnotexist") + }.should raise_error(Fiddle::DLError) + end +end diff --git a/spec/ruby/library/find/find_spec.rb b/spec/ruby/library/find/find_spec.rb new file mode 100644 index 0000000000..7cd76fa01b --- /dev/null +++ b/spec/ruby/library/find/find_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require 'find' + +describe "Find.find" do + before :each do + FindDirSpecs.create_mock_dirs + end + + after :each do + FindDirSpecs.delete_mock_dirs + end + + describe "when called without a block" do + it "returns an Enumerator" do + Find.find(FindDirSpecs.mock_dir).should be_an_instance_of(Enumerator) + Find.find(FindDirSpecs.mock_dir).to_a.sort.should == FindDirSpecs.expected_paths + end + end + + it "should recursively yield every file in the directory" do + a = [] + + Find.find(FindDirSpecs.mock_dir) do |file| + a << file + end + + a.sort.should == FindDirSpecs.expected_paths + end +end diff --git a/spec/ruby/library/find/fixtures/common.rb b/spec/ruby/library/find/fixtures/common.rb new file mode 100644 index 0000000000..99f3bbb45a --- /dev/null +++ b/spec/ruby/library/find/fixtures/common.rb @@ -0,0 +1,178 @@ +module FindDirSpecs + def self.mock_dir(dirs = ['find_specs_mock']) + @mock_dir ||= tmp("") + File.join @mock_dir, dirs + end + + # The names of the fixture directories and files used by + # various Find specs. + def self.mock_dir_files + unless @mock_dir_files + @mock_dir_files = %w[ + .dotfile + .dotsubdir/.dotfile + .dotsubdir/nondotfile + + deeply/.dotfile + deeply/nested/.dotfile.ext + deeply/nested/directory/structure/.ext + deeply/nested/directory/structure/bar + deeply/nested/directory/structure/baz + deeply/nested/directory/structure/file_one + deeply/nested/directory/structure/file_one.ext + deeply/nested/directory/structure/foo + deeply/nondotfile + + file_one.ext + file_two.ext + + dir_filename_ordering + dir/filename_ordering + + nondotfile + + subdir_one/.dotfile + subdir_one/nondotfile + subdir_two/nondotfile + subdir_two/nondotfile.ext + + brace/a + brace/a.js + brace/a.erb + brace/a.js.rjs + brace/a.html.erb + + special/+ + + special/^ + special/$ + + special/( + special/) + special/[ + special/] + special/{ + special/} + + special/test{1}/file[1] + ] + + platform_is_not :windows do + @mock_dir_files += %w[ + special/* + special/? + + special/| + ] + end + end + + @mock_dir_files + end + + def self.create_mock_dirs + tmp('') # make sure there is an tmpdir + umask = File.umask 0 + begin + mock_dir_files.each do |name| + file = File.join mock_dir, name + mkdir_p File.dirname(file) + touch file + end + ensure + File.umask umask + end + end + + def self.delete_mock_dirs + rm_r mock_dir + end + + def self.expected_paths + unless @expected_paths + @expected_paths = %w[ + .dotfile + + .dotsubdir + .dotsubdir/.dotfile + .dotsubdir/nondotfile + + deeply + deeply/.dotfile + + deeply/nested + deeply/nested/.dotfile.ext + + deeply/nested/directory + + deeply/nested/directory/structure + deeply/nested/directory/structure/.ext + deeply/nested/directory/structure/bar + deeply/nested/directory/structure/baz + deeply/nested/directory/structure/file_one + deeply/nested/directory/structure/file_one.ext + deeply/nested/directory/structure/foo + deeply/nondotfile + + file_one.ext + file_two.ext + + dir_filename_ordering + + dir + dir/filename_ordering + + nondotfile + + subdir_one + subdir_one/.dotfile + subdir_one/nondotfile + + subdir_two + subdir_two/nondotfile + subdir_two/nondotfile.ext + + brace + brace/a + brace/a.js + brace/a.erb + brace/a.js.rjs + brace/a.html.erb + + special + special/+ + + special/^ + special/$ + + special/( + special/) + special/[ + special/] + special/{ + special/} + + special/test{1} + special/test{1}/file[1] + ] + + platform_is_not :windows do + @expected_paths += %w[ + special/* + special/? + + special/| + ] + end + + @expected_paths.map! do |file| + File.join(mock_dir, file) + end + + @expected_paths << mock_dir + @expected_paths.sort! + end + + @expected_paths + end +end diff --git a/spec/ruby/library/find/prune_spec.rb b/spec/ruby/library/find/prune_spec.rb new file mode 100644 index 0000000000..25dc2cbf3e --- /dev/null +++ b/spec/ruby/library/find/prune_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'find' + +describe "Find.prune" do + it "should throw :prune" do + msg = catch(:prune) do + Find.prune + end + + msg.should == nil + end +end diff --git a/spec/ruby/library/getoptlong/each_option_spec.rb b/spec/ruby/library/getoptlong/each_option_spec.rb new file mode 100644 index 0000000000..c6d82af86d --- /dev/null +++ b/spec/ruby/library/getoptlong/each_option_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/each' + +describe "GetoptLong#each_option" do + it_behaves_like :getoptlong_each, :each_option +end diff --git a/spec/ruby/library/getoptlong/each_spec.rb b/spec/ruby/library/getoptlong/each_spec.rb new file mode 100644 index 0000000000..d9022f02af --- /dev/null +++ b/spec/ruby/library/getoptlong/each_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/each' + +describe "GetoptLong#each" do + it_behaves_like :getoptlong_each, :each +end diff --git a/spec/ruby/library/getoptlong/error_message_spec.rb b/spec/ruby/library/getoptlong/error_message_spec.rb new file mode 100644 index 0000000000..1ed9419f6c --- /dev/null +++ b/spec/ruby/library/getoptlong/error_message_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'getoptlong' + +describe "GetoptLong#error_message" do + it "returns nil if no error occurred" do + opts = GetoptLong.new + opts.error_message.should == nil + end + + it "returns the error message of the last error that occurred" do + argv [] do + opts = GetoptLong.new + opts.quiet = true + opts.get + -> { + opts.ordering = GetoptLong::PERMUTE + }.should raise_error(ArgumentError) { |e| + e.message.should == "argument error" + opts.error_message.should == "argument error" + } + end + end +end diff --git a/spec/ruby/library/getoptlong/get_option_spec.rb b/spec/ruby/library/getoptlong/get_option_spec.rb new file mode 100644 index 0000000000..3cb2044379 --- /dev/null +++ b/spec/ruby/library/getoptlong/get_option_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/get' + +describe "GetoptLong#get_option" do + it_behaves_like :getoptlong_get, :get_option +end diff --git a/spec/ruby/library/getoptlong/get_spec.rb b/spec/ruby/library/getoptlong/get_spec.rb new file mode 100644 index 0000000000..a8ec586fc9 --- /dev/null +++ b/spec/ruby/library/getoptlong/get_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/get' + +describe "GetoptLong#get" do + it_behaves_like :getoptlong_get, :get +end diff --git a/spec/ruby/library/getoptlong/initialize_spec.rb b/spec/ruby/library/getoptlong/initialize_spec.rb new file mode 100644 index 0000000000..782edbd981 --- /dev/null +++ b/spec/ruby/library/getoptlong/initialize_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'getoptlong' + +describe "GetoptLong#initialize" do + it "sets ordering to REQUIRE_ORDER if ENV['POSIXLY_CORRECT'] is set" do + begin + old_env_value = ENV["POSIXLY_CORRECT"] + ENV["POSIXLY_CORRECT"] = "" + + opt = GetoptLong.new + opt.ordering.should == GetoptLong::REQUIRE_ORDER + ensure + ENV["POSIXLY_CORRECT"] = old_env_value + end + end + + it "sets ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is not set" do + begin + old_env_value = ENV["POSIXLY_CORRECT"] + ENV["POSIXLY_CORRECT"] = nil + + opt = GetoptLong.new + opt.ordering.should == GetoptLong::PERMUTE + ensure + ENV["POSIXLY_CORRECT"] = old_env_value + end + end +end diff --git a/spec/ruby/library/getoptlong/ordering_spec.rb b/spec/ruby/library/getoptlong/ordering_spec.rb new file mode 100644 index 0000000000..695d1cafa7 --- /dev/null +++ b/spec/ruby/library/getoptlong/ordering_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require 'getoptlong' + +describe "GetoptLong#ordering=" do + it "raises an ArgumentError if called after processing has started" do + argv [ "--size", "10k", "--verbose" ] do + opts = GetoptLong.new([ '--size', GetoptLong::REQUIRED_ARGUMENT ], + [ '--verbose', GetoptLong::NO_ARGUMENT ]) + opts.quiet = true + opts.get + + -> { + opts.ordering = GetoptLong::PERMUTE + }.should raise_error(ArgumentError) + end + end + + it "raises an ArgumentError if given an invalid value" do + opts = GetoptLong.new + + -> { + opts.ordering = 12345 + }.should raise_error(ArgumentError) + end + + it "does not allow changing ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is set" do + begin + old_env_value = ENV['POSIXLY_CORRECT'] + ENV['POSIXLY_CORRECT'] = "" + + opts = GetoptLong.new + opts.ordering = GetoptLong::PERMUTE + opts.ordering.should == GetoptLong::REQUIRE_ORDER + ensure + ENV['POSIXLY_CORRECT'] = old_env_value + end + end +end diff --git a/spec/ruby/library/getoptlong/set_options_spec.rb b/spec/ruby/library/getoptlong/set_options_spec.rb new file mode 100644 index 0000000000..36b9c579c4 --- /dev/null +++ b/spec/ruby/library/getoptlong/set_options_spec.rb @@ -0,0 +1,98 @@ +require_relative '../../spec_helper' +require 'getoptlong' + +describe "GetoptLong#set_options" do + before :each do + @opts = GetoptLong.new + end + + it "allows setting command line options" do + argv ["--size", "10k", "-v", "arg1", "arg2"] do + @opts.set_options( + ["--size", GetoptLong::REQUIRED_ARGUMENT], + ["--verbose", "-v", GetoptLong::NO_ARGUMENT] + ) + + @opts.get.should == ["--size", "10k"] + @opts.get.should == ["--verbose", ""] + @opts.get.should == nil + end + end + + it "discards previously defined command line options" do + argv ["--size", "10k", "-v", "arg1", "arg2"] do + @opts.set_options( + ["--size", GetoptLong::REQUIRED_ARGUMENT], + ["--verbose", "-v", GetoptLong::NO_ARGUMENT] + ) + + @opts.set_options( + ["-s", "--size", GetoptLong::REQUIRED_ARGUMENT], + ["-v", GetoptLong::NO_ARGUMENT] + ) + + @opts.get.should == ["-s", "10k"] + @opts.get.should == ["-v", ""] + @opts.get.should == nil + end + end + + it "raises an ArgumentError if too many argument flags where given" do + argv [] do + -> { + @opts.set_options(["--size", GetoptLong::NO_ARGUMENT, GetoptLong::REQUIRED_ARGUMENT]) + }.should raise_error(ArgumentError) + end + end + + it "raises a RuntimeError if processing has already started" do + argv [] do + @opts.get + -> { + @opts.set_options() + }.should raise_error(RuntimeError) + end + end + + it "raises an ArgumentError if no argument flag was given" do + argv [] do + -> { + @opts.set_options(["--size"]) + }.should raise_error(ArgumentError) + end + end + + it "raises an ArgumentError if one of the given arguments is not an Array" do + argv [] do + -> { + @opts.set_options( + ["--size", GetoptLong::REQUIRED_ARGUMENT], + "test") + }.should raise_error(ArgumentError) + end + end + + it "raises an ArgumentError if the same option is given twice" do + argv [] do + -> { + @opts.set_options( + ["--size", GetoptLong::NO_ARGUMENT], + ["--size", GetoptLong::OPTIONAL_ARGUMENT]) + }.should raise_error(ArgumentError) + + -> { + @opts.set_options( + ["--size", GetoptLong::NO_ARGUMENT], + ["-s", "--size", GetoptLong::OPTIONAL_ARGUMENT]) + }.should raise_error(ArgumentError) + end + end + + it "raises an ArgumentError if the given option is invalid" do + argv [] do + -> { + @opts.set_options(["-size", GetoptLong::NO_ARGUMENT]) + }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/getoptlong/shared/each.rb b/spec/ruby/library/getoptlong/shared/each.rb new file mode 100644 index 0000000000..b534e24c0f --- /dev/null +++ b/spec/ruby/library/getoptlong/shared/each.rb @@ -0,0 +1,18 @@ +describe :getoptlong_each, shared: true do + before :each do + @opts = GetoptLong.new( + [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--query', '-q', GetoptLong::NO_ARGUMENT ], + [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ] + ) + end + + it "passes each argument/value pair to the block" do + argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do + pairs = [] + @opts.send(@method) { |arg, val| pairs << [ arg, val ] } + pairs.should == [ [ "--size", "10k" ], [ "--verbose", "" ], [ "--query", ""] ] + end + end +end diff --git a/spec/ruby/library/getoptlong/shared/get.rb b/spec/ruby/library/getoptlong/shared/get.rb new file mode 100644 index 0000000000..f44cf583d2 --- /dev/null +++ b/spec/ruby/library/getoptlong/shared/get.rb @@ -0,0 +1,62 @@ +describe :getoptlong_get, shared: true do + before :each do + @opts = GetoptLong.new( + [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--query', '-q', GetoptLong::NO_ARGUMENT ], + [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ] + ) + @opts.quiet = true # silence using $deferr + end + + it "returns the next option name and its argument as an Array" do + argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do + @opts.send(@method).should == [ "--size", "10k" ] + @opts.send(@method).should == [ "--verbose", "" ] + @opts.send(@method).should == [ "--query", ""] + @opts.send(@method).should == nil + end + end + + it "shifts ARGV on each call" do + argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do + @opts.send(@method) + ARGV.should == [ "-v", "-q", "a.txt", "b.txt" ] + + @opts.send(@method) + ARGV.should == [ "-q", "a.txt", "b.txt" ] + + @opts.send(@method) + ARGV.should == [ "a.txt", "b.txt" ] + + @opts.send(@method) + ARGV.should == [ "a.txt", "b.txt" ] + end + end + + it "terminates processing when encountering '--'" do + argv [ "--size", "10k", "--", "-v", "-q", "a.txt", "b.txt" ] do + @opts.send(@method) + ARGV.should == ["--", "-v", "-q", "a.txt", "b.txt"] + + @opts.send(@method) + ARGV.should == ["-v", "-q", "a.txt", "b.txt"] + + @opts.send(@method) + ARGV.should == ["-v", "-q", "a.txt", "b.txt"] + end + end + + it "raises a if an argument was required, but none given" do + argv [ "--size" ] do + -> { @opts.send(@method) }.should raise_error(GetoptLong::MissingArgument) + end + end + + # https://bugs.ruby-lang.org/issues/13858 + it "returns multiline argument" do + argv [ "--size=\n10k\n" ] do + @opts.send(@method).should == [ "--size", "\n10k\n" ] + end + end +end diff --git a/spec/ruby/library/getoptlong/terminate_spec.rb b/spec/ruby/library/getoptlong/terminate_spec.rb new file mode 100644 index 0000000000..a12d1df2ef --- /dev/null +++ b/spec/ruby/library/getoptlong/terminate_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'getoptlong' + +describe "GetoptLong#terminate" do + before :each do + @opts = GetoptLong.new( + [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--query', '-q', GetoptLong::NO_ARGUMENT ], + [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ] + ) + end + + it "terminates option processing" do + argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do + @opts.get.should == [ "--size", "10k" ] + @opts.terminate + @opts.get.should == nil + end + end + + it "returns self when option processing is terminated" do + @opts.terminate.should == @opts + end + + it "returns nil when option processing was already terminated" do + @opts.terminate + @opts.terminate.should == nil + end +end diff --git a/spec/ruby/library/getoptlong/terminated_spec.rb b/spec/ruby/library/getoptlong/terminated_spec.rb new file mode 100644 index 0000000000..6108a7f6e9 --- /dev/null +++ b/spec/ruby/library/getoptlong/terminated_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'getoptlong' + +describe "GetoptLong#terminated?" do + it "returns true if option processing has terminated" do + argv [ "--size", "10k" ] do + opts = GetoptLong.new(["--size", GetoptLong::REQUIRED_ARGUMENT]) + opts.should_not.terminated? + + opts.get.should == ["--size", "10k"] + opts.should_not.terminated? + + opts.get.should == nil + opts.should.terminated? + end + end +end diff --git a/spec/ruby/library/io-wait/wait_readable_spec.rb b/spec/ruby/library/io-wait/wait_readable_spec.rb new file mode 100644 index 0000000000..d7473f029f --- /dev/null +++ b/spec/ruby/library/io-wait/wait_readable_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' + +describe "IO#wait_readable" do + before :each do + @io = File.new(__FILE__ ) + end + + after :each do + @io.close + end + + it "waits for the IO to become readable with no timeout" do + @io.wait_readable.should == @io + end + + it "waits for the IO to become readable with the given timeout" do + @io.wait_readable(1).should == @io + end + + it "waits for the IO to become readable with the given large timeout" do + @io.wait_readable(365 * 24 * 60 * 60).should == @io + end + + it "can be interrupted" do + rd, wr = IO.pipe + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + rd.wait_readable(10) + end + + Thread.pass until t.stop? + t.kill + t.join + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 + ensure + rd.close + wr.close + end +end diff --git a/spec/ruby/library/io-wait/wait_spec.rb b/spec/ruby/library/io-wait/wait_spec.rb new file mode 100644 index 0000000000..6a3890a401 --- /dev/null +++ b/spec/ruby/library/io-wait/wait_spec.rb @@ -0,0 +1,162 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/io' + +describe "IO#wait" do + before :each do + @io = File.new(__FILE__ ) + + if /mswin|mingw/ =~ RUBY_PLATFORM + require 'socket' + @r, @w = Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0) + else + @r, @w = IO.pipe + end + end + + after :each do + @io.close unless @io.closed? + + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + context "[events, timeout] passed" do + it "returns events mask when the READABLE event is ready during the timeout" do + @w.write('data to read') + @r.wait(IO::READABLE, 2).should == IO::READABLE + end + + it "returns events mask when the WRITABLE event is ready during the timeout" do + @w.wait(IO::WRITABLE, 0).should == IO::WRITABLE + end + + it "waits for the READABLE event to be ready" do + @r.wait(IO::READABLE, 0).should == nil + + @w.write('data to read') + @r.wait(IO::READABLE, 0).should_not == nil + end + + it "waits for the WRITABLE event to be ready" do + written_bytes = IOSpec.exhaust_write_buffer(@w) + @w.wait(IO::WRITABLE, 0).should == nil + + @r.read(written_bytes) + @w.wait(IO::WRITABLE, 0).should_not == nil + end + + it "returns nil when the READABLE event is not ready during the timeout" do + @w.wait(IO::READABLE, 0).should == nil + end + + it "returns nil when the WRITABLE event is not ready during the timeout" do + IOSpec.exhaust_write_buffer(@w) + @w.wait(IO::WRITABLE, 0).should == nil + end + + it "raises IOError when io is closed (closed stream (IOError))" do + @io.close + -> { @io.wait(IO::READABLE, 0) }.should raise_error(IOError, "closed stream") + end + + it "raises ArgumentError when events is not positive" do + -> { @w.wait(0, 0) }.should raise_error(ArgumentError, "Events must be positive integer!") + -> { @w.wait(-1, 0) }.should raise_error(ArgumentError, "Events must be positive integer!") + end + + it "changes thread status to 'sleep' when waits for READABLE event" do + t = Thread.new { @r.wait(IO::READABLE, 10) } + sleep 1 + t.status.should == 'sleep' + t.kill + t.join # Thread#kill doesn't wait for the thread to end + end + + # https://github.com/ruby/ruby/actions/runs/11948300522/job/33305664284?pr=12139 + platform_is_not :windows do + it "changes thread status to 'sleep' when waits for WRITABLE event" do + IOSpec.exhaust_write_buffer(@w) + + t = Thread.new { @w.wait(IO::WRITABLE, 10) } + sleep 1 + t.status.should == 'sleep' + t.kill + t.join # Thread#kill doesn't wait for the thread to end + end + end + + it "can be interrupted when waiting for READABLE event" do + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + @r.wait(IO::READABLE, 10) + end + + Thread.pass until t.stop? + t.kill + t.join # Thread#kill doesn't wait for the thread to end + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 + end + + it "can be interrupted when waiting for WRITABLE event" do + IOSpec.exhaust_write_buffer(@w) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + @w.wait(IO::WRITABLE, 10) + end + + Thread.pass until t.stop? + t.kill + t.join # Thread#kill doesn't wait for the thread to end + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 + end + end + + context "[timeout, mode] passed" do + it "accepts :r, :read, :readable mode to check READABLE event" do + @io.wait(0, :r).should == @io + @io.wait(0, :read).should == @io + @io.wait(0, :readable).should == @io + end + + it "accepts :w, :write, :writable mode to check WRITABLE event" do + @io.wait(0, :w).should == @io + @io.wait(0, :write).should == @io + @io.wait(0, :writable).should == @io + end + + it "accepts :rw, :read_write, :readable_writable mode to check READABLE and WRITABLE events" do + @io.wait(0, :rw).should == @io + @io.wait(0, :read_write).should == @io + @io.wait(0, :readable_writable).should == @io + end + + it "accepts a list of modes" do + @io.wait(0, :r, :w, :rw).should == @io + end + + it "accepts timeout and mode in any order" do + @io.wait(0, :r).should == @io + @io.wait(:r, 0).should == @io + @io.wait(:r, 0, :w).should == @io + end + + it "raises ArgumentError when passed wrong Symbol value as mode argument" do + -> { @io.wait(0, :wrong) }.should raise_error(ArgumentError, "unsupported mode: wrong") + end + + it "raises ArgumentError when several Integer arguments passed" do + -> { @w.wait(0, 10, :r) }.should raise_error(ArgumentError, "timeout given more than once") + end + + it "raises IOError when io is closed (closed stream (IOError))" do + @io.close + -> { @io.wait(0, :r) }.should raise_error(IOError, "closed stream") + end + end +end diff --git a/spec/ruby/library/io-wait/wait_writable_spec.rb b/spec/ruby/library/io-wait/wait_writable_spec.rb new file mode 100644 index 0000000000..2017817caa --- /dev/null +++ b/spec/ruby/library/io-wait/wait_writable_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/io' + +describe "IO#wait_writable" do + it "waits for the IO to become writable with no timeout" do + STDOUT.wait_writable.should == STDOUT + end + + it "waits for the IO to become writable with the given timeout" do + STDOUT.wait_writable(1).should == STDOUT + end + + it "waits for the IO to become writable with the given large timeout" do + # Represents one year and is larger than a 32-bit int + STDOUT.wait_writable(365 * 24 * 60 * 60).should == STDOUT + end + + it "can be interrupted" do + rd, wr = IO.pipe + IOSpec.exhaust_write_buffer(wr) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + wr.wait_writable(10) + end + + Thread.pass until t.stop? + t.kill + t.join + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 + ensure + rd.close unless rd.closed? + wr.close unless wr.closed? + end +end diff --git a/spec/ruby/library/ipaddr/hton_spec.rb b/spec/ruby/library/ipaddr/hton_spec.rb new file mode 100644 index 0000000000..9c0b821abf --- /dev/null +++ b/spec/ruby/library/ipaddr/hton_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'ipaddr' + +describe "IPAddr#hton" do + + it "converts IPAddr to network byte order" do + addr = '' + IPAddr.new("1234:5678:9abc:def0:1234:5678:9abc:def0").hton.each_byte do |c| + addr += sprintf("%02x", c) + end + addr.should == "123456789abcdef0123456789abcdef0" + addr = '' + IPAddr.new("123.45.67.89").hton.each_byte do |c| + addr += sprintf("%02x", c) + end + addr.should == sprintf("%02x%02x%02x%02x", 123, 45, 67, 89) + end + +end + +describe "IPAddr#new_ntoh" do + + it "creates a new IPAddr using hton notation" do + a = IPAddr.new("3ffe:505:2::") + IPAddr.new_ntoh(a.hton).to_s.should == "3ffe:505:2::" + a = IPAddr.new("192.168.2.1") + IPAddr.new_ntoh(a.hton).to_s.should == "192.168.2.1" + end + +end diff --git a/spec/ruby/library/ipaddr/ipv4_conversion_spec.rb b/spec/ruby/library/ipaddr/ipv4_conversion_spec.rb new file mode 100644 index 0000000000..1128c16dd2 --- /dev/null +++ b/spec/ruby/library/ipaddr/ipv4_conversion_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' +require 'ipaddr' + +describe "IPAddr#ipv4_compat" do + + it "should ipv4_compat?" do + a = IPAddr.new("::192.168.1.2") + a.to_s.should == "::192.168.1.2" + a.to_string.should == "0000:0000:0000:0000:0000:0000:c0a8:0102" + a.family.should == Socket::AF_INET6 + a.should.ipv4_compat? + b = a.native + b.to_s.should == "192.168.1.2" + b.family.should == Socket::AF_INET + b.should_not.ipv4_compat? + + a = IPAddr.new("192.168.1.2") + b = a.ipv4_compat + b.to_s.should == "::192.168.1.2" + b.family.should == Socket::AF_INET6 + end + +end + +describe "IPAddr#ipv4_mapped" do + + it "should ipv4_mapped" do + a = IPAddr.new("::ffff:192.168.1.2") + a.to_s.should == "::ffff:192.168.1.2" + a.to_string.should == "0000:0000:0000:0000:0000:ffff:c0a8:0102" + a.family.should == Socket::AF_INET6 + a.should.ipv4_mapped? + b = a.native + b.to_s.should == "192.168.1.2" + b.family.should == Socket::AF_INET + b.should_not.ipv4_mapped? + + a = IPAddr.new("192.168.1.2") + b = a.ipv4_mapped + b.to_s.should == "::ffff:192.168.1.2" + b.family.should == Socket::AF_INET6 + end + +end diff --git a/spec/ruby/library/ipaddr/new_spec.rb b/spec/ruby/library/ipaddr/new_spec.rb new file mode 100644 index 0000000000..2c0f44acf2 --- /dev/null +++ b/spec/ruby/library/ipaddr/new_spec.rb @@ -0,0 +1,92 @@ +require_relative '../../spec_helper' +require 'ipaddr' + +describe "IPAddr#new" do + it "initializes IPAddr" do + ->{ IPAddr.new("3FFE:505:ffff::/48") }.should_not raise_error + ->{ IPAddr.new("0:0:0:1::") }.should_not raise_error + ->{ IPAddr.new("2001:200:300::/48") }.should_not raise_error + end + + it "initializes IPAddr ipv6 address with short notation" do + a = IPAddr.new + a.to_s.should == "::" + a.to_string.should == "0000:0000:0000:0000:0000:0000:0000:0000" + a.family.should == Socket::AF_INET6 + end + + it "initializes IPAddr ipv6 address with long notation" do + a = IPAddr.new("0123:4567:89ab:cdef:0ABC:DEF0:1234:5678") + a.to_s.should == "123:4567:89ab:cdef:abc:def0:1234:5678" + a.to_string.should == "0123:4567:89ab:cdef:0abc:def0:1234:5678" + a.family.should == Socket::AF_INET6 + end + + it "initializes IPAddr ipv6 address with / subnet notation" do + a = IPAddr.new("3ffe:505:2::/48") + a.to_s.should == "3ffe:505:2::" + a.to_string.should == "3ffe:0505:0002:0000:0000:0000:0000:0000" + a.family.should == Socket::AF_INET6 + a.should_not.ipv4? + a.should.ipv6? + a.inspect.should == "#<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ffff:ffff:ffff:0000:0000:0000:0000:0000>" + end + + it "initializes IPAddr ipv6 address with mask subnet notation" do + a = IPAddr.new("3ffe:505:2::/ffff:ffff:ffff::") + a.to_s.should == "3ffe:505:2::" + a.to_string.should == "3ffe:0505:0002:0000:0000:0000:0000:0000" + a.family.should == Socket::AF_INET6 + end + + it "initializes IPAddr ipv4 address with all zeroes" do + a = IPAddr.new("0.0.0.0") + a.to_s.should == "0.0.0.0" + a.to_string.should == "0.0.0.0" + a.family.should == Socket::AF_INET + end + + it "initializes IPAddr ipv4 address" do + a = IPAddr.new("192.168.1.2") + a.to_s.should == "192.168.1.2" + a.to_string.should == "192.168.1.2" + a.family.should == Socket::AF_INET + a.should.ipv4? + a.should_not.ipv6? + end + + it "initializes IPAddr ipv4 address with / subnet notation" do + a = IPAddr.new("192.168.1.2/24") + a.to_s.should == "192.168.1.0" + a.to_string.should == "192.168.1.0" + a.family.should == Socket::AF_INET + a.inspect.should == "#<IPAddr: IPv4:192.168.1.0/255.255.255.0>" + end + + it "initializes IPAddr ipv4 address with subnet mask" do + a = IPAddr.new("192.168.1.2/255.255.255.0") + a.to_s.should == "192.168.1.0" + a.to_string.should == "192.168.1.0" + a.family.should == Socket::AF_INET + end + + it "initializes IPAddr ipv4 mapped address with subnet mask" do + a = IPAddr.new("::1:192.168.1.2/120") + a.to_s.should == "::1:c0a8:100" + a.to_string.should == "0000:0000:0000:0000:0000:0001:c0a8:0100" + a.family.should == Socket::AF_INET6 + end + + it "raises on incorrect IPAddr strings" do + [ + ["::1/255.255.255.0"], + [IPAddr.new("::1").to_i], + ["::ffff:192.168.1.2/120", Socket::AF_INET], + ["[192.168.1.2]/120"], + ].each { |args| + ->{ + IPAddr.new(*args) + }.should raise_error(ArgumentError) + } + end +end diff --git a/spec/ruby/library/ipaddr/operator_spec.rb b/spec/ruby/library/ipaddr/operator_spec.rb new file mode 100644 index 0000000000..f90c56009c --- /dev/null +++ b/spec/ruby/library/ipaddr/operator_spec.rb @@ -0,0 +1,82 @@ +require_relative '../../spec_helper' +require 'ipaddr' + +describe "IPAddr Operator" do + before do + @in6_addr_any = IPAddr.new() + @a = IPAddr.new("3ffe:505:2::/48") + @b = IPAddr.new("0:0:0:1::") + @c = IPAddr.new("ffff:ffff::") + end + + it "bitwises or" do + (@a | @b).to_s.should == "3ffe:505:2:1::" + a = @a + a |= @b + a.to_s.should == "3ffe:505:2:1::" + @a.to_s.should == "3ffe:505:2::" + (@a | 0x00000000000000010000000000000000).to_s.should == "3ffe:505:2:1::" + end + + it "bitwises and" do + (@a & @c).to_s.should == "3ffe:505::" + a = @a + a &= @c + a.to_s.should == "3ffe:505::" + @a.to_s.should == "3ffe:505:2::" + (@a & 0xffffffff000000000000000000000000).to_s.should == "3ffe:505::" + end + + it "bitshifts right" do + (@a >> 16).to_s.should == "0:3ffe:505:2::" + a = @a + a >>= 16 + a.to_s.should == "0:3ffe:505:2::" + @a.to_s.should == "3ffe:505:2::" + end + + it "bitshifts left" do + (@a << 16).to_s.should == "505:2::" + a = @a + a <<= 16 + a.to_s.should == "505:2::" + @a.to_s.should == "3ffe:505:2::" + end + + it "inverts" do + a = ~@in6_addr_any + a.to_s.should == "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" + @in6_addr_any.to_s.should == "::" + end + + it "tests for equality" do + @a.should == IPAddr.new("3ffe:505:2::") + @a.should_not == IPAddr.new("3ffe:505:3::") + end + + # https://bugs.ruby-lang.org/issues/12799 + it "tests for equality correctly if object cannot be converted to IPAddr" do + IPAddr.new("1.1.1.1").should_not == "sometext" + end + + it "sets a mask" do + a = @a.mask(32) + a.to_s.should == "3ffe:505::" + @a.to_s.should == "3ffe:505:2::" + end + + it "checks whether an address is included in a range" do + @a.should include(IPAddr.new("3ffe:505:2::")) + @a.should include(IPAddr.new("3ffe:505:2::1")) + @a.should_not include(IPAddr.new("3ffe:505:3::")) + net1 = IPAddr.new("192.168.2.0/24") + net1.should include(IPAddr.new("192.168.2.0")) + net1.should include(IPAddr.new("192.168.2.255")) + net1.should_not include(IPAddr.new("192.168.3.0")) + # test with integer parameter + int = (192 << 24) + (168 << 16) + (2 << 8) + 13 + + net1.should include(int) + net1.should_not include(int+255) + end +end diff --git a/spec/ruby/library/ipaddr/reverse_spec.rb b/spec/ruby/library/ipaddr/reverse_spec.rb new file mode 100644 index 0000000000..6ebb343269 --- /dev/null +++ b/spec/ruby/library/ipaddr/reverse_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require 'ipaddr' + +describe "IPAddr#reverse" do + it "generates the reverse DNS lookup entry" do + IPAddr.new("3ffe:505:2::f").reverse.should == "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa" + IPAddr.new("192.168.2.1").reverse.should == "1.2.168.192.in-addr.arpa" + end +end + +describe "IPAddr#ip6_arpa" do + it "converts an IPv6 address into the reverse DNS lookup representation according to RFC3172" do + IPAddr.new("3ffe:505:2::f").ip6_arpa.should == "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa" + ->{ + IPAddr.new("192.168.2.1").ip6_arpa + }.should raise_error(ArgumentError) + end +end + +describe "IPAddr#ip6_int" do + it "converts an IPv6 address into the reverse DNS lookup representation according to RFC1886" do + IPAddr.new("3ffe:505:2::f").ip6_int.should == "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.int" + ->{ + IPAddr.new("192.168.2.1").ip6_int + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/ipaddr/to_s_spec.rb b/spec/ruby/library/ipaddr/to_s_spec.rb new file mode 100644 index 0000000000..2a9a027909 --- /dev/null +++ b/spec/ruby/library/ipaddr/to_s_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'ipaddr' + +describe "IPAddr#to_s" do + + it "displays IPAddr using short notation" do + IPAddr.new("0:0:0:1::").to_s.should == "0:0:0:1::" + IPAddr.new("2001:200:300::/48").to_s.should == "2001:200:300::" + IPAddr.new("[2001:200:300::]/48").to_s.should == "2001:200:300::" + IPAddr.new("3ffe:505:2::1").to_s.should == "3ffe:505:2::1" + end + +end + +describe "IPAddr#to_string" do + it "displays an IPAddr using full notation" do + IPAddr.new("3ffe:505:2::1").to_string.should == "3ffe:0505:0002:0000:0000:0000:0000:0001" + end + +end diff --git a/spec/ruby/library/irb/fixtures/irb.rb b/spec/ruby/library/irb/fixtures/irb.rb new file mode 100644 index 0000000000..8d386dfda1 --- /dev/null +++ b/spec/ruby/library/irb/fixtures/irb.rb @@ -0,0 +1,3 @@ +a = 10 + +binding.irb # rubocop:disable Lint/Debugger diff --git a/spec/ruby/library/irb/irb_spec.rb b/spec/ruby/library/irb/irb_spec.rb new file mode 100644 index 0000000000..2607c7ef33 --- /dev/null +++ b/spec/ruby/library/irb/irb_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'tmpdir' + +describe "Binding#irb" do + it "creates an IRB session with the binding in scope" do + irb_fixture = fixture __FILE__, "irb.rb" + envs = %w[IRBRC HOME XDG_CONFIG_HOME].to_h {|e| [e, nil]} + + out = Dir.mktmpdir do |dir| + IO.popen([envs, *ruby_exe, irb_fixture, chdir: dir], "r+") do |pipe| + pipe.puts "a ** 2" + pipe.puts "exit" + pipe.readlines.map(&:chomp).reject(&:empty?) + end + end + + out.last(3).should == ["a ** 2", "100", "exit"] + end +end diff --git a/spec/ruby/library/logger/device/close_spec.rb b/spec/ruby/library/logger/device/close_spec.rb new file mode 100644 index 0000000000..1db5d582a7 --- /dev/null +++ b/spec/ruby/library/logger/device/close_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger::LogDevice#close" do + before :each do + @file_path = tmp("test_log.log") + @log_file = File.open(@file_path, "w+") + + # Avoid testing this with STDERR, we don't want to be closing that. + @device = Logger::LogDevice.new(@log_file) + end + + after :each do + @log_file.close unless @log_file.closed? + rm_r @file_path + end + + it "closes the LogDevice's stream" do + @device.close + -> { @device.write("Test") }.should complain(/\Alog shifting failed\./) + end +end diff --git a/spec/ruby/library/logger/device/new_spec.rb b/spec/ruby/library/logger/device/new_spec.rb new file mode 100644 index 0000000000..26a38c2b8c --- /dev/null +++ b/spec/ruby/library/logger/device/new_spec.rb @@ -0,0 +1,47 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger::LogDevice#new" do + before :each do + @file_path = tmp("test_log.log") + @log_file = File.open(@file_path, "w+") + end + + after :each do + @log_file.close unless @log_file.closed? + rm_r @file_path + end + + it "creates a new log device" do + l = Logger::LogDevice.new(@log_file) + l.dev.should be_kind_of(File) + end + + it "receives an IO object to log there as first argument" do + @log_file.should be_kind_of(IO) + l = Logger::LogDevice.new(@log_file) + l.write("foo") + @log_file.rewind + @log_file.readlines.first.should == "foo" + end + + it "creates a File if the IO object does not exist" do + path = tmp("test_logger_file") + l = Logger::LogDevice.new(path) + l.write("Test message") + l.close + + File.should.exist?(path) + File.open(path) do |f| + f.readlines.should_not be_empty + end + + rm_r path + end + + it "receives options via a hash as second argument" do + -> { + Logger::LogDevice.new(STDERR, shift_age: 8, shift_size: 10) + }.should_not raise_error + end +end diff --git a/spec/ruby/library/logger/device/write_spec.rb b/spec/ruby/library/logger/device/write_spec.rb new file mode 100644 index 0000000000..87ecf2ad6a --- /dev/null +++ b/spec/ruby/library/logger/device/write_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger::LogDevice#write" do + before :each do + @file_path = tmp("test_log.log") + @log_file = File.open(@file_path, "w+") + # Avoid testing this with STDERR, we don't want to be closing that. + @device = Logger::LogDevice.new(@log_file) + end + + after :each do + @log_file.close unless @log_file.closed? + rm_r @file_path + end + + it "writes a message to the device" do + @device.write "This is a test message" + @log_file.rewind + @log_file.readlines.first.should == "This is a test message" + end + + it "can create a file and writes empty message" do + path = tmp("you_should_not_see_me") + logdevice = Logger::LogDevice.new(path) + logdevice.write("") + logdevice.close + + File.open(path) do |f| + messages = f.readlines + messages.size.should == 1 + messages.first.should =~ /#.*/ # only a comment + end + + rm_r path + end + + it "fails if the device is already closed" do + @device.close + -> { @device.write "foo" }.should complain(/\Alog shifting failed\./) + end +end diff --git a/spec/ruby/library/logger/fixtures/common.rb b/spec/ruby/library/logger/fixtures/common.rb new file mode 100644 index 0000000000..d369c64a24 --- /dev/null +++ b/spec/ruby/library/logger/fixtures/common.rb @@ -0,0 +1,9 @@ +require 'logger' + +module LoggerSpecs + + def self.strip_date(str) + str.gsub(/[A-Z].*\[.*\]/, "").lstrip + end + +end diff --git a/spec/ruby/library/logger/logger/add_spec.rb b/spec/ruby/library/logger/logger/add_spec.rb new file mode 100644 index 0000000000..3f709e18ba --- /dev/null +++ b/spec/ruby/library/logger/logger/add_spec.rb @@ -0,0 +1,81 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#add" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "writes a new message to the logger" do + @logger.add(Logger::WARN, "Test") + @log_file.rewind + message = @log_file.readlines.last + LoggerSpecs.strip_date(message).should == "WARN -- : Test\n" + end + + it "receives a severity" do + @logger.log(Logger::INFO, "Info message") + @logger.log(Logger::DEBUG, "Debug message") + @logger.log(Logger::WARN, "Warn message") + @logger.log(Logger::ERROR, "Error message") + @logger.log(Logger::FATAL, "Fatal message") + + @log_file.rewind + + info, debug, warn, error, fatal = @log_file.readlines + + LoggerSpecs.strip_date(info).should == "INFO -- : Info message\n" + LoggerSpecs.strip_date(debug).should == "DEBUG -- : Debug message\n" + LoggerSpecs.strip_date(warn).should == "WARN -- : Warn message\n" + LoggerSpecs.strip_date(error).should == "ERROR -- : Error message\n" + LoggerSpecs.strip_date(fatal).should == "FATAL -- : Fatal message\n" + end + + it "receives a message" do + @logger.log(nil, "test") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readline).should == "ANY -- : test\n" + end + + it "receives a program name" do + @logger.log(nil, "test", "TestApp") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readline).should == "ANY -- TestApp: test\n" + end + + it "receives a block" do + -> { + @logger.log(nil, "test", "TestApp") do + 1+1 + end + }.should_not raise_error + end + + it "calls the block if message is nil" do + temp = 0 + -> { + @logger.log(nil, nil, "TestApp") do + temp = 1+1 + end + }.should_not raise_error + temp.should == 2 + end + + it "ignores the block if the message is not nil" do + temp = 0 + -> { + @logger.log(nil, "not nil", "TestApp") do + temp = 1+1 + end + }.should_not raise_error + temp.should == 0 + end +end diff --git a/spec/ruby/library/logger/logger/close_spec.rb b/spec/ruby/library/logger/logger/close_spec.rb new file mode 100644 index 0000000000..81aac2a6cd --- /dev/null +++ b/spec/ruby/library/logger/logger/close_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#close" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "closes the logging device" do + @logger.close + -> { @logger.add(nil, "Foo") }.should complain(/\Alog writing failed\./) + end +end diff --git a/spec/ruby/library/logger/logger/datetime_format_spec.rb b/spec/ruby/library/logger/logger/datetime_format_spec.rb new file mode 100644 index 0000000000..582b34bfda --- /dev/null +++ b/spec/ruby/library/logger/logger/datetime_format_spec.rb @@ -0,0 +1,60 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#datetime_format" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "returns the date format used for the logs" do + format = "%Y-%d" + @logger.datetime_format = format + @logger.datetime_format.should == format + end + + it "returns nil logger is using the default date format" do + @logger.datetime_format.should == nil + end +end + +describe "Logger#datetime_format=" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "sets the date format for the logs" do + @logger.datetime_format = "%Y" + @logger.datetime_format.should == "%Y" + @logger.add(Logger::WARN, "Test message") + @log_file.rewind + + regex = /2[0-9]{3}.*Test message/ + @log_file.readlines.first.should =~ regex + end + + it "follows the Time#strftime format" do + -> { @logger.datetime_format = "%Y-%m" }.should_not raise_error + + regex = /\d{4}-\d{2}-\d{2}oo-\w+ar/ + @logger.datetime_format = "%Foo-%Bar" + @logger.add(nil, "Test message") + @log_file.rewind + @log_file.readlines.first.should =~ regex + end +end diff --git a/spec/ruby/library/logger/logger/debug_spec.rb b/spec/ruby/library/logger/logger/debug_spec.rb new file mode 100644 index 0000000000..9375ab0cc6 --- /dev/null +++ b/spec/ruby/library/logger/logger/debug_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#debug?" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "returns true if severity level allows debug messages" do + @logger.level = Logger::DEBUG + @logger.should.debug? + end + + it "returns false if severity level does not allow debug messages" do + @logger.level = Logger::WARN + @logger.should_not.debug? + end +end + +describe "Logger#debug" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "logs a DEBUG message" do + @logger.debug("test") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "DEBUG -- : test\n" + end + + it "accepts an application name with a block" do + @logger.debug("MyApp") { "Test message" } + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "DEBUG -- MyApp: Test message\n" + end +end diff --git a/spec/ruby/library/logger/logger/error_spec.rb b/spec/ruby/library/logger/logger/error_spec.rb new file mode 100644 index 0000000000..42f1dbd883 --- /dev/null +++ b/spec/ruby/library/logger/logger/error_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#error?" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "returns true if severity level allows printing errors" do + @logger.level = Logger::INFO + @logger.should.error? + end + + it "returns false if severity level does not allow errors" do + @logger.level = Logger::FATAL + @logger.should_not.error? + end +end + +describe "Logger#error" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "logs a ERROR message" do + @logger.error("test") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "ERROR -- : test\n" + end + + it "accepts an application name with a block" do + @logger.error("MyApp") { "Test message" } + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "ERROR -- MyApp: Test message\n" + end + +end diff --git a/spec/ruby/library/logger/logger/fatal_spec.rb b/spec/ruby/library/logger/logger/fatal_spec.rb new file mode 100644 index 0000000000..f12fa3a89f --- /dev/null +++ b/spec/ruby/library/logger/logger/fatal_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#fatal?" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "returns true if severity level allows fatal messages" do + @logger.level = Logger::FATAL + @logger.should.fatal? + end + + it "returns false if severity level does not allow fatal messages" do + @logger.level = Logger::UNKNOWN + @logger.should_not.fatal? + end +end + +describe "Logger#fatal" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "logs a FATAL message" do + @logger.fatal("test") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "FATAL -- : test\n" + end + + it "accepts an application name with a block" do + @logger.fatal("MyApp") { "Test message" } + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "FATAL -- MyApp: Test message\n" + end + +end diff --git a/spec/ruby/library/logger/logger/info_spec.rb b/spec/ruby/library/logger/logger/info_spec.rb new file mode 100644 index 0000000000..eb5dca48dd --- /dev/null +++ b/spec/ruby/library/logger/logger/info_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#info?" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "returns true if severity level allows info messages" do + @logger.level = Logger::INFO + @logger.should.info? + end + + it "returns false if severity level does not allow info messages" do + @logger.level = Logger::FATAL + @logger.should_not.info? + end +end + +describe "Logger#info" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "logs a INFO message" do + @logger.info("test") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "INFO -- : test\n" + end + + it "accepts an application name with a block" do + @logger.info("MyApp") { "Test message" } + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "INFO -- MyApp: Test message\n" + end + +end diff --git a/spec/ruby/library/logger/logger/new_spec.rb b/spec/ruby/library/logger/logger/new_spec.rb new file mode 100644 index 0000000000..3db20e7432 --- /dev/null +++ b/spec/ruby/library/logger/logger/new_spec.rb @@ -0,0 +1,118 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#new" do + + before :each do + @file_path = tmp("test_log.log") + @log_file = File.open(@file_path, "w+") + end + + after :each do + @log_file.close unless @log_file.closed? + rm_r @file_path + end + + it "creates a new logger object" do + l = Logger.new(STDERR) + -> { l.add(Logger::WARN, "Foo") }.should output_to_fd(/Foo/, STDERR) + end + + it "receives a logging device as first argument" do + l = Logger.new(@log_file) + l.add(Logger::WARN, "Test message") + + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readline).should == "WARN -- : Test message\n" + l.close + end + + it "receives a frequency rotation as second argument" do + -> { Logger.new(@log_file, "daily") }.should_not raise_error + -> { Logger.new(@log_file, "weekly") }.should_not raise_error + -> { Logger.new(@log_file, "monthly") }.should_not raise_error + end + + it "also receives a number of log files to keep as second argument" do + -> { Logger.new(@log_file, 1).close }.should_not raise_error + end + + it "receives a maximum logfile size as third argument" do + # This should create 2 small log files, logfile_test and logfile_test.0 + # in /tmp, each one with a different message. + path = tmp("logfile_test.log") + + l = Logger.new(path, 2, 5) + l.add Logger::WARN, "foo" + l.add Logger::WARN, "bar" + + File.should.exist?(path) + File.should.exist?(path + ".0") + + # first line will be a comment so we'll have to skip it. + f = File.open(path) + f1 = File.open("#{path}.0") + LoggerSpecs.strip_date(f1.readlines.last).should == "WARN -- : foo\n" + LoggerSpecs.strip_date(f.readlines.last).should == "WARN -- : bar\n" + + l.close + f.close + f1.close + rm_r path, "#{path}.0" + end + + it "receives level symbol as keyword argument" do + logger = Logger.new(STDERR, level: :info) + logger.level.should == Logger::INFO + end + + it "receives level as keyword argument" do + logger = Logger.new(STDERR, level: Logger::INFO) + logger.level.should == Logger::INFO + end + + it "receives progname as keyword argument" do + progname = "progname" + + logger = Logger.new(STDERR, progname: progname) + logger.progname.should == progname + end + + it "receives datetime_format as keyword argument" do + datetime_format = "%H:%M:%S" + + logger = Logger.new(STDERR, datetime_format: datetime_format) + logger.datetime_format.should == datetime_format + end + + it "receives formatter as keyword argument" do + formatter = Class.new do + def call(_severity, _time, _progname, _msg); end + end.new + + logger = Logger.new(STDERR, formatter: formatter) + logger.formatter.should == formatter + end + + it "receives shift_period_suffix " do + shift_period_suffix = "%Y-%m-%d" + path = tmp("shift_period_suffix_test.log") + now = Time.now + tomorrow = Time.at(now.to_i + 60 * 60 * 24) + logger = Logger.new(path, 'daily', shift_period_suffix: shift_period_suffix) + + logger.add Logger::INFO, 'message' + + Time.stub!(:now).and_return(tomorrow) + logger.add Logger::INFO, 'second message' + + shifted_path = "#{path}.#{now.strftime(shift_period_suffix)}" + + File.should.exist?(shifted_path) + + logger.close + + rm_r path, shifted_path + end + +end diff --git a/spec/ruby/library/logger/logger/unknown_spec.rb b/spec/ruby/library/logger/logger/unknown_spec.rb new file mode 100644 index 0000000000..b174b8b2c9 --- /dev/null +++ b/spec/ruby/library/logger/logger/unknown_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#unknown" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "logs a message with unknown severity" do + @logger.unknown "Test" + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "ANY -- : Test\n" + end + + it "defaults the priority value to 5 and text value to ANY" do + @logger.unknown "Test" + @log_file.rewind + message = LoggerSpecs.strip_date(@log_file.readlines.first)[0..2] + message.should == "ANY" + Logger::UNKNOWN.should == 5 + end + + it "receives empty messages" do + -> { @logger.unknown("") }.should_not raise_error + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "ANY -- : \n" + end +end diff --git a/spec/ruby/library/logger/logger/warn_spec.rb b/spec/ruby/library/logger/logger/warn_spec.rb new file mode 100644 index 0000000000..0bca34824a --- /dev/null +++ b/spec/ruby/library/logger/logger/warn_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' + +describe "Logger#warn?" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "returns true if severity level allows printing warn messages" do + @logger.level = Logger::WARN + @logger.should.warn? + end + + it "returns false if severity level does not allow printing warn messages" do + @logger.level = Logger::FATAL + @logger.should_not.warn? + end +end + +describe "Logger#warn" do + before :each do + @path = tmp("test_log.log") + @log_file = File.open(@path, "w+") + @logger = Logger.new(@path) + end + + after :each do + @logger.close + @log_file.close unless @log_file.closed? + rm_r @path + end + + it "logs a WARN message" do + @logger.warn("test") + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "WARN -- : test\n" + end + + it "accepts an application name with a block" do + @logger.warn("MyApp") { "Test message" } + @log_file.rewind + LoggerSpecs.strip_date(@log_file.readlines.first).should == "WARN -- MyApp: Test message\n" + end + +end diff --git a/spec/ruby/library/logger/severity_spec.rb b/spec/ruby/library/logger/severity_spec.rb new file mode 100644 index 0000000000..e9bc850c33 --- /dev/null +++ b/spec/ruby/library/logger/severity_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'logger' + +describe "Logger::Severity" do + it "defines Logger severity constants" do + Logger::DEBUG.should == 0 + Logger::INFO.should == 1 + Logger::WARN.should == 2 + Logger::ERROR.should == 3 + Logger::FATAL.should == 4 + Logger::UNKNOWN.should == 5 + end +end diff --git a/spec/ruby/library/matrix/I_spec.rb b/spec/ruby/library/matrix/I_spec.rb new file mode 100644 index 0000000000..6eeffe8e98 --- /dev/null +++ b/spec/ruby/library/matrix/I_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/identity' + +describe "Matrix.I" do + it_behaves_like :matrix_identity, :I +end diff --git a/spec/ruby/library/matrix/antisymmetric_spec.rb b/spec/ruby/library/matrix/antisymmetric_spec.rb new file mode 100644 index 0000000000..200df703cb --- /dev/null +++ b/spec/ruby/library/matrix/antisymmetric_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' + +require 'matrix' + +describe "Matrix#antisymmetric?" do + it "returns true for an antisymmetric Matrix" do + Matrix[[0, -2, Complex(1, 3)], [2, 0, 5], [-Complex(1, 3), -5, 0]].antisymmetric?.should be_true + end + + it "returns true for a 0x0 empty matrix" do + Matrix.empty.antisymmetric?.should be_true + end + + it "returns false for non-antisymmetric matrices" do + [ + Matrix[[1, 2, 3], [4, 5, 6], [7, 8, 9]], + Matrix[[1, -2, 3], [2, 0, 6], [-3, -6, 0]], # wrong diagonal element + Matrix[[0, 2, -3], [2, 0, 6], [-3, 6, 0]] # only signs wrong + ].each do |matrix| + matrix.antisymmetric?.should be_false + end + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.antisymmetric? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/build_spec.rb b/spec/ruby/library/matrix/build_spec.rb new file mode 100644 index 0000000000..6d8017a3df --- /dev/null +++ b/spec/ruby/library/matrix/build_spec.rb @@ -0,0 +1,73 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.build" do + + it "returns a Matrix object of the given size" do + m = Matrix.build(3, 4){1} + m.should be_an_instance_of(Matrix) + m.row_size.should == 3 + m.column_size.should == 4 + end + + it "builds the Matrix using the given block" do + Matrix.build(2, 3){|col, row| 10*col - row}.should == + Matrix[[0, -1, -2], [10, 9, 8]] + end + + it "iterates through the first row, then the second, ..." do + acc = [] + Matrix.build(2, 3){|*args| acc << args} + acc.should == [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]] + end + + it "returns an Enumerator is no block is given" do + enum = Matrix.build(2, 1) + enum.should be_an_instance_of(Enumerator) + enum.each{1}.should == Matrix[[1], [1]] + end + + it "requires integers as parameters" do + -> { Matrix.build("1", "2"){1} }.should raise_error(TypeError) + -> { Matrix.build(nil, nil){1} }.should raise_error(TypeError) + -> { Matrix.build(1..2){1} }.should raise_error(TypeError) + end + + it "requires non-negative integers" do + -> { Matrix.build(-1, 1){1} }.should raise_error(ArgumentError) + -> { Matrix.build(+1,-1){1} }.should raise_error(ArgumentError) + end + + it "returns empty Matrix if one argument is zero" do + m = Matrix.build(0, 3){ + raise "Should not yield" + } + m.should be_empty + m.column_size.should == 3 + + m = Matrix.build(3, 0){ + raise "Should not yield" + } + m.should be_empty + m.row_size.should == 3 + end + + it "tries to calls :to_int on arguments" do + int = mock('int') + int.should_receive(:to_int).twice.and_return(2) + Matrix.build(int, int){ 1 }.should == Matrix[ [1,1], [1,1] ] + end + + it "builds an nxn Matrix when given only one argument" do + m = Matrix.build(3){1} + m.row_size.should == 3 + m.column_size.should == 3 + end +end + +describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.build(3){1}.should be_an_instance_of(MatrixSub) + end +end diff --git a/spec/ruby/library/matrix/clone_spec.rb b/spec/ruby/library/matrix/clone_spec.rb new file mode 100644 index 0000000000..74e5bf157e --- /dev/null +++ b/spec/ruby/library/matrix/clone_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#clone" do + before :each do + @a = Matrix[[1, 2], [3, 4], [5, 6]] + end + + it "returns a shallow copy of the matrix" do + b = @a.clone + @a.should_not equal(b) + b.should be_kind_of(Matrix) + b.should == @a + 0.upto(@a.row_size - 1) do |i| + @a.row(i).should_not equal(b.row(i)) + end + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.clone.should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/coerce_spec.rb b/spec/ruby/library/matrix/coerce_spec.rb new file mode 100644 index 0000000000..4022f00236 --- /dev/null +++ b/spec/ruby/library/matrix/coerce_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#coerce" do + it "allows the division of integer by a Matrix " do + (1/Matrix[[0,1],[-1,0]]).should == Matrix[[0,-1],[1,0]] + end +end diff --git a/spec/ruby/library/matrix/collect_spec.rb b/spec/ruby/library/matrix/collect_spec.rb new file mode 100644 index 0000000000..bba640213b --- /dev/null +++ b/spec/ruby/library/matrix/collect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/collect' + +describe "Matrix#collect" do + it_behaves_like :collect, :collect +end diff --git a/spec/ruby/library/matrix/column_size_spec.rb b/spec/ruby/library/matrix/column_size_spec.rb new file mode 100644 index 0000000000..041914e5b9 --- /dev/null +++ b/spec/ruby/library/matrix/column_size_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#column_size" do + it "returns the number of columns" do + Matrix[ [1,2], [3,4] ].column_size.should == 2 + end + + it "returns 0 for empty matrices" do + Matrix[ [], [] ].column_size.should == 0 + Matrix[ ].column_size.should == 0 + end +end diff --git a/spec/ruby/library/matrix/column_spec.rb b/spec/ruby/library/matrix/column_spec.rb new file mode 100644 index 0000000000..1f3c80964a --- /dev/null +++ b/spec/ruby/library/matrix/column_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#column" do + before :all do + @m = Matrix[[1,2,3], [2,3,4]] + end + + it "returns a Vector when called without a block" do + @m.column(1).should == Vector[2,3] + end + + it "yields each element in the column to the block" do + a = [] + @m.column(1) {|n| a << n } + a.should == [2,3] + end + + it "counts backwards for negative argument" do + @m.column(-1).should == Vector[3, 4] + end + + it "returns self when called with a block" do + @m.column(0) { |x| x }.should equal(@m) + end + + it "returns nil when out of bounds" do + @m.column(3).should == nil + end + + it "never yields when out of bounds" do + -> { @m.column(3){ raise } }.should_not raise_error + -> { @m.column(-4){ raise } }.should_not raise_error + end +end diff --git a/spec/ruby/library/matrix/column_vector_spec.rb b/spec/ruby/library/matrix/column_vector_spec.rb new file mode 100644 index 0000000000..47e866a8d5 --- /dev/null +++ b/spec/ruby/library/matrix/column_vector_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.column_vector" do + + it "returns a single column Matrix when called with an Array" do + m = Matrix.column_vector([4,5,6]) + m.should be_an_instance_of(Matrix) + m.should == Matrix[ [4],[5],[6] ] + end + + it "returns an empty Matrix when called with an empty Array" do + m = Matrix.column_vector([]) + m.should be_an_instance_of(Matrix) + m.row_size.should == 0 + m.column_size.should == 1 + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.column_vector([4,5,6]).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/column_vectors_spec.rb b/spec/ruby/library/matrix/column_vectors_spec.rb new file mode 100644 index 0000000000..b0cb6f914c --- /dev/null +++ b/spec/ruby/library/matrix/column_vectors_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#column_vectors" do + + before :each do + @vectors = Matrix[ [1,2], [3,4] ].column_vectors + end + + it "returns an Array" do + Matrix[ [1,2], [3,4] ].column_vectors.should be_an_instance_of(Array) + end + + it "returns an Array of Vectors" do + @vectors.all? {|v| v.should be_an_instance_of(Vector)} + end + + it "returns each column as a Vector" do + @vectors.should == [Vector[1,3], Vector[2,4]] + end + + it "returns an empty Array for empty matrices" do + Matrix[ [] ].column_vectors.should == [] + end + +end diff --git a/spec/ruby/library/matrix/columns_spec.rb b/spec/ruby/library/matrix/columns_spec.rb new file mode 100644 index 0000000000..3095fdd7af --- /dev/null +++ b/spec/ruby/library/matrix/columns_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.columns" do + before :each do + @a = [1, 2] + @b = [3, 4] + @m = Matrix.columns([@a, @b]) + end + + it "creates a Matrix from argument columns" do + @m.should be_an_instance_of(Matrix) + @m.column(0).to_a.should == @a + @m.column(1).to_a.should == @b + end + + it "accepts Vectors as argument columns" do + m = Matrix.columns([Vector[*@a], Vector[*@b]]) + m.should == @m + m.column(0).to_a.should == @a + m.column(1).to_a.should == @b + end + + it "handles empty matrices" do + e = Matrix.columns([]) + e.row_size.should == 0 + e.column_size.should == 0 + e.should == Matrix[] + + v = Matrix.columns([[],[],[]]) + v.row_size.should == 0 + v.column_size.should == 3 + v.should == Matrix[[], [], []].transpose + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.columns([[1]]).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/conj_spec.rb b/spec/ruby/library/matrix/conj_spec.rb new file mode 100644 index 0000000000..ecee95c255 --- /dev/null +++ b/spec/ruby/library/matrix/conj_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/conjugate' + +describe "Matrix#conj" do + it_behaves_like :matrix_conjugate, :conj +end diff --git a/spec/ruby/library/matrix/conjugate_spec.rb b/spec/ruby/library/matrix/conjugate_spec.rb new file mode 100644 index 0000000000..682bd41d94 --- /dev/null +++ b/spec/ruby/library/matrix/conjugate_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/conjugate' + +describe "Matrix#conjugate" do + it_behaves_like :matrix_conjugate, :conjugate +end diff --git a/spec/ruby/library/matrix/constructor_spec.rb b/spec/ruby/library/matrix/constructor_spec.rb new file mode 100644 index 0000000000..70d77babbb --- /dev/null +++ b/spec/ruby/library/matrix/constructor_spec.rb @@ -0,0 +1,65 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.[]" do + + it "requires arrays as parameters" do + -> { Matrix[5] }.should raise_error(TypeError) + -> { Matrix[nil] }.should raise_error(TypeError) + -> { Matrix[1..2] }.should raise_error(TypeError) + -> { Matrix[[1, 2], 3] }.should raise_error(TypeError) + end + + it "creates an empty Matrix with no arguments" do + m = Matrix[] + m.column_size.should == 0 + m.row_size.should == 0 + end + + it "raises for non-rectangular matrices" do + ->{ Matrix[ [0], [0,1] ] }.should \ + raise_error(Matrix::ErrDimensionMismatch) + ->{ Matrix[ [0,1], [0,1,2], [0,1] ]}.should \ + raise_error(Matrix::ErrDimensionMismatch) + end + + it "accepts vector arguments" do + a = Matrix[Vector[1, 2], Vector[3, 4]] + a.should be_an_instance_of(Matrix) + a.should == Matrix[ [1, 2], [3, 4] ] + end + + it "tries to calls :to_ary on arguments" do + array = mock('ary') + array.should_receive(:to_ary).and_return([1,2]) + Matrix[array, [3,4] ].should == Matrix[ [1,2], [3,4] ] + end + + + it "returns a Matrix object" do + Matrix[ [1] ].should be_an_instance_of(Matrix) + end + + it "can create an nxn Matrix" do + m = Matrix[ [20,30], [40.5, 9] ] + m.row_size.should == 2 + m.column_size.should == 2 + m.column(0).should == Vector[20, 40.5] + m.column(1).should == Vector[30, 9] + m.row(0).should == Vector[20, 30] + m.row(1).should == Vector[40.5, 9] + end + + it "can create a 0xn Matrix" do + m = Matrix[ [], [], [] ] + m.row_size.should == 3 + m.column_size.should == 0 + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub[ [20,30], [40.5, 9] ].should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/det_spec.rb b/spec/ruby/library/matrix/det_spec.rb new file mode 100644 index 0000000000..aa7086cacf --- /dev/null +++ b/spec/ruby/library/matrix/det_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/determinant' +require 'matrix' + +describe "Matrix#det" do + it_behaves_like :determinant, :det +end diff --git a/spec/ruby/library/matrix/determinant_spec.rb b/spec/ruby/library/matrix/determinant_spec.rb new file mode 100644 index 0000000000..825c9907b1 --- /dev/null +++ b/spec/ruby/library/matrix/determinant_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/determinant' +require 'matrix' + +describe "Matrix#determinant" do + it_behaves_like :determinant, :determinant +end diff --git a/spec/ruby/library/matrix/diagonal_spec.rb b/spec/ruby/library/matrix/diagonal_spec.rb new file mode 100644 index 0000000000..ef9738e73e --- /dev/null +++ b/spec/ruby/library/matrix/diagonal_spec.rb @@ -0,0 +1,72 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.diagonal" do + before :each do + @m = Matrix.diagonal(10, 11, 12, 13, 14) + end + + it "returns an object of type Matrix" do + @m.should be_kind_of(Matrix) + end + + it "returns a square Matrix of the right size" do + @m.column_size.should == 5 + @m.row_size.should == 5 + end + + it "sets the diagonal to the arguments" do + (0..4).each do |i| + @m[i, i].should == i + 10 + end + end + + it "fills all non-diagonal cells with 0" do + (0..4).each do |i| + (0..4).each do |j| + if i != j + @m[i, j].should == 0 + end + end + end + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.diagonal(1).should be_an_instance_of(MatrixSub) + end + end +end + +describe "Matrix.diagonal?" do + it "returns true for a diagonal Matrix" do + Matrix.diagonal([1, 2, 3]).diagonal?.should be_true + end + + it "returns true for a zero square Matrix" do + Matrix.zero(3).diagonal?.should be_true + end + + it "returns false for a non diagonal square Matrix" do + Matrix[[0, 1], [0, 0]].diagonal?.should be_false + Matrix[[1, 2, 3], [1, 2, 3], [1, 2, 3]].diagonal?.should be_false + end + + it "returns true for an empty 0x0 matrix" do + Matrix.empty(0,0).diagonal?.should be_true + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.diagonal? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/divide_spec.rb b/spec/ruby/library/matrix/divide_spec.rb new file mode 100644 index 0000000000..2e3bb85bf6 --- /dev/null +++ b/spec/ruby/library/matrix/divide_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#/" do + before :each do + @a = Matrix[ [1, 2], [3, 4] ] + @b = Matrix[ [4, 5], [6, 7] ] + @c = Matrix[ [1.2, 2.4], [3.6, 4.8] ] + end + + it "returns the result of dividing self by another Matrix" do + (@a / @b).should be_close_to_matrix([[2.5, -1.5], [1.5, -0.5]]) + end + + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns the result of dividing self by a Fixnum" do + (@a / 2).should == Matrix[ [0, 1], [1, 2] ] + end + + it "returns the result of dividing self by a Bignum" do + (@a / bignum_value).should == Matrix[ [0, 0], [0, 0] ] + end + end + + it "returns the result of dividing self by a Float" do + (@c / 1.2).should == Matrix[ [1, 2], [3, 4] ] + end + + it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do + -> { @a / Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch) + end + + it "returns an instance of Matrix" do + (@a / @b).should be_kind_of(Matrix) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + m = MatrixSub.ins + (m/m).should be_an_instance_of(MatrixSub) + (m/1).should be_an_instance_of(MatrixSub) + end + end + + it "raises a TypeError if other is of wrong type" do + -> { @a / nil }.should raise_error(TypeError) + -> { @a / "a" }.should raise_error(TypeError) + -> { @a / [ [1, 2] ] }.should raise_error(TypeError) + -> { @a / Object.new }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/matrix/each_spec.rb b/spec/ruby/library/matrix/each_spec.rb new file mode 100644 index 0000000000..f3b0f01867 --- /dev/null +++ b/spec/ruby/library/matrix/each_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#each" do + before :all do + @m = Matrix[ [1, 2, 3], [4, 5, 6] ] + @result = (1..6).to_a + end + + it "returns an Enumerator when called without a block" do + enum = @m.each + enum.should be_an_instance_of(Enumerator) + enum.to_a.should == @result + end + + it "returns self" do + @m.each{}.should equal(@m) + end + + it "yields the elements starting with the those of the first row" do + a = [] + @m.each {|x| a << x} + a.should == @result + end +end + +describe "Matrix#each with an argument" do + before :all do + @m = Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ] + @t = Matrix[ [1, 2], [3, 4], [5, 6], [7, 8] ] + end + + it "raises an ArgumentError for unrecognized argument" do + -> { + @m.each("all"){} + }.should raise_error(ArgumentError) + -> { + @m.each(nil){} + }.should raise_error(ArgumentError) + -> { + @m.each(:left){} + }.should raise_error(ArgumentError) + end + + it "yields the rights elements when passed :diagonal" do + @m.each(:diagonal).to_a.should == [1, 6] + @t.each(:diagonal).to_a.should == [1, 4] + end + + it "yields the rights elements when passed :off_diagonal" do + @m.each(:off_diagonal).to_a.should == [2, 3, 4, 5, 7, 8] + @t.each(:off_diagonal).to_a.should == [2, 3, 5, 6, 7, 8] + end + + it "yields the rights elements when passed :lower" do + @m.each(:lower).to_a.should == [1, 5, 6] + @t.each(:lower).to_a.should == [1, 3, 4, 5, 6, 7, 8] + end + + it "yields the rights elements when passed :strict_lower" do + @m.each(:strict_lower).to_a.should == [5] + @t.each(:strict_lower).to_a.should == [3, 5, 6, 7, 8] + end + + it "yields the rights elements when passed :strict_upper" do + @m.each(:strict_upper).to_a.should == [2, 3, 4, 7, 8] + @t.each(:strict_upper).to_a.should == [2] + end + + it "yields the rights elements when passed :upper" do + @m.each(:upper).to_a.should == [1, 2, 3, 4, 6, 7, 8] + @t.each(:upper).to_a.should == [1, 2, 4] + end +end diff --git a/spec/ruby/library/matrix/each_with_index_spec.rb b/spec/ruby/library/matrix/each_with_index_spec.rb new file mode 100644 index 0000000000..a005b88621 --- /dev/null +++ b/spec/ruby/library/matrix/each_with_index_spec.rb @@ -0,0 +1,81 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#each_with_index" do + before :all do + @m = Matrix[ [1, 2, 3], [4, 5, 6] ] + @result = [ + [1, 0, 0], + [2, 0, 1], + [3, 0, 2], + [4, 1, 0], + [5, 1, 1], + [6, 1, 2] + ] + end + + it "returns an Enumerator when called without a block" do + enum = @m.each_with_index + enum.should be_an_instance_of(Enumerator) + enum.to_a.should == @result + end + + it "returns self" do + @m.each_with_index{}.should equal(@m) + end + + it "yields the elements starting with the those of the first row" do + a = [] + @m.each_with_index {|x, r, c| a << [x, r, c]} + a.should == @result + end +end + +describe "Matrix#each_with_index with an argument" do + before :all do + @m = Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ] + @t = Matrix[ [1, 2], [3, 4], [5, 6], [7, 8] ] + end + + it "raises an ArgumentError for unrecognized argument" do + -> { + @m.each_with_index("all"){} + }.should raise_error(ArgumentError) + -> { + @m.each_with_index(nil){} + }.should raise_error(ArgumentError) + -> { + @m.each_with_index(:left){} + }.should raise_error(ArgumentError) + end + + it "yields the rights elements when passed :diagonal" do + @m.each_with_index(:diagonal).to_a.should == [[1, 0, 0], [6, 1, 1]] + @t.each_with_index(:diagonal).to_a.should == [[1, 0, 0], [4, 1, 1]] + end + + it "yields the rights elements when passed :off_diagonal" do + @m.each_with_index(:off_diagonal).to_a.should == [[2, 0, 1], [3, 0, 2], [4, 0, 3], [5, 1, 0], [7, 1, 2], [8, 1, 3]] + @t.each_with_index(:off_diagonal).to_a.should == [[2, 0, 1], [3, 1, 0], [5, 2, 0], [6, 2, 1], [7, 3, 0], [8, 3, 1]] + end + + it "yields the rights elements when passed :lower" do + @m.each_with_index(:lower).to_a.should == [[1, 0, 0], [5, 1, 0], [6, 1, 1]] + @t.each_with_index(:lower).to_a.should == [[1, 0, 0], [3, 1, 0], [4, 1, 1], [5, 2, 0], [6, 2, 1], [7, 3, 0], [8, 3, 1]] + end + + it "yields the rights elements when passed :strict_lower" do + @m.each_with_index(:strict_lower).to_a.should == [[5, 1, 0]] + @t.each_with_index(:strict_lower).to_a.should == [[3, 1, 0], [5, 2, 0], [6, 2, 1], [7, 3, 0], [8, 3, 1]] + end + + it "yields the rights elements when passed :strict_upper" do + @m.each_with_index(:strict_upper).to_a.should == [[2, 0, 1], [3, 0, 2], [4, 0, 3], [7, 1, 2], [8, 1, 3]] + @t.each_with_index(:strict_upper).to_a.should == [[2, 0, 1]] + end + + it "yields the rights elements when passed :upper" do + @m.each_with_index(:upper).to_a.should == [[1, 0, 0], [2, 0, 1], [3, 0, 2], [4, 0, 3], [6, 1, 1], [7, 1, 2], [8, 1, 3]] + @t.each_with_index(:upper).to_a.should == [[1, 0, 0], [2, 0, 1], [4, 1, 1]] + end +end diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb new file mode 100644 index 0000000000..67f9dd1c19 --- /dev/null +++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::EigenvalueDecomposition#eigenvalue_matrix" do + it "returns a diagonal matrix with the eigenvalues on the diagonal" do + Matrix[[14, 16], [-6, -6]].eigensystem.eigenvalue_matrix.should == Matrix[[6, 0],[0, 2]] + Matrix[[1, 1], [-1, 1]].eigensystem.eigenvalue_matrix.should == Matrix[[Complex(1,1), 0],[0, Complex(1,-1)]] + end +end diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb new file mode 100644 index 0000000000..7552b7616c --- /dev/null +++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::EigenvalueDecomposition#eigenvalues" do + it "returns an array of complex eigenvalues for a rotation matrix" do + Matrix[[ 1, 1], + [-1, 1]].eigensystem.eigenvalues.sort_by{|v| v.imag}.should == + [ Complex(1, -1), Complex(1, 1)] + end + + it "returns an array of real eigenvalues for a symmetric matrix" do + Matrix[[1, 2], + [2, 1]].eigensystem.eigenvalues.sort.map!{|x| x.round(10)}.should == + [ -1, 3 ] + end + + it "returns an array of real eigenvalues for a matrix" do + Matrix[[14, 16], + [-6, -6]].eigensystem.eigenvalues.sort.map!{|x| x.round(10)}.should == + [ 2, 6 ] + end +end diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb new file mode 100644 index 0000000000..09f229ee15 --- /dev/null +++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::EigenvalueDecomposition#eigenvector_matrix" do + it "returns a complex eigenvector matrix given a rotation matrix" do + # Fix me: should test for linearity, not for equality + Matrix[[ 1, 1], + [-1, 1]].eigensystem.eigenvector_matrix.should == + Matrix[[1, 1], + [Complex(0, 1), Complex(0, -1)]] + end + + it "returns an real eigenvector matrix for a symmetric matrix" do + # Fix me: should test for linearity, not for equality + Matrix[[1, 2], + [2, 1]].eigensystem.eigenvector_matrix.should == + Matrix[[0.7071067811865475, 0.7071067811865475], + [-0.7071067811865475, 0.7071067811865475]] + end +end diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb new file mode 100644 index 0000000000..2b6ce74ea8 --- /dev/null +++ b/spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::EigenvalueDecomposition#eigenvectors" do + it "returns an array of complex eigenvectors for a rotation matrix" do + # Fix me: should test for linearity, not for equality + Matrix[[ 1, 1], + [-1, 1]].eigensystem.eigenvectors.should == + [ Vector[1, Complex(0, 1)], + Vector[1, Complex(0, -1)] + ] + end + + it "returns an array of real eigenvectors for a symmetric matrix" do + # Fix me: should test for linearity, not for equality + Matrix[[1, 2], + [2, 1]].eigensystem.eigenvectors.should == + [ Vector[0.7071067811865475, -0.7071067811865475], + Vector[0.7071067811865475, 0.7071067811865475] + ] + end +end diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb new file mode 100644 index 0000000000..8438f63133 --- /dev/null +++ b/spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::EigenvalueDecomposition#initialize" do + it "raises an error if argument is not a matrix" do + -> { + Matrix::EigenvalueDecomposition.new([[]]) + }.should raise_error(TypeError) + -> { + Matrix::EigenvalueDecomposition.new(42) + }.should raise_error(TypeError) + end + + it "raises an error if matrix is not square" do + -> { + Matrix::EigenvalueDecomposition.new(Matrix[[1, 2]]) + }.should raise_error(Matrix::ErrDimensionMismatch) + end + + it "never hangs" do + m = Matrix[ [0,0,0,0,0], [0,0,0,0,1], [0,0,0,1,0], [1,1,0,0,1], [1,0,1,0,1] ] + Matrix::EigenvalueDecomposition.new(m).should_not == "infinite loop" + end +end diff --git a/spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb b/spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb new file mode 100644 index 0000000000..8be41a5720 --- /dev/null +++ b/spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::EigenvalueDecomposition#to_a" do + before :each do + @a = Matrix[[14, 16], [-6, -6]] + @e = Matrix::EigenvalueDecomposition.new(@a) + end + + it "returns an array of with [V, D, V.inv]" do + @e.to_a.should == [@e.v, @e.d, @e.v_inv] + end + + it "returns a factorization" do + v, d, v_inv = @e.to_a + (v * d * v_inv).map{|e| e.round(10)}.should == @a + end +end diff --git a/spec/ruby/library/matrix/element_reference_spec.rb b/spec/ruby/library/matrix/element_reference_spec.rb new file mode 100644 index 0000000000..b950d1c391 --- /dev/null +++ b/spec/ruby/library/matrix/element_reference_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#[]" do + + before :all do + @m = Matrix[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]] + end + + it "returns element at (i, j)" do + (0..3).each do |i| + (0..2).each do |j| + @m[i, j].should == (i * 3) + j + end + end + end + + it "returns nil for an invalid index pair" do + @m[8,1].should be_nil + @m[1,8].should be_nil + end + +end diff --git a/spec/ruby/library/matrix/empty_spec.rb b/spec/ruby/library/matrix/empty_spec.rb new file mode 100644 index 0000000000..5f294711db --- /dev/null +++ b/spec/ruby/library/matrix/empty_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#empty?" do + it "returns true when the Matrix is empty" do + Matrix[ ].empty?.should be_true + Matrix[ [], [], [] ].empty?.should be_true + Matrix[ [], [], [] ].transpose.empty?.should be_true + end + + it "returns false when the Matrix has elements" do + Matrix[ [1, 2] ].empty?.should be_false + Matrix[ [1], [2] ].empty?.should be_false + end + + it "doesn't accept any parameter" do + ->{ + Matrix[ [1, 2] ].empty?(42) + }.should raise_error(ArgumentError) + end +end + +describe "Matrix.empty" do + it "returns an empty matrix of the requested size" do + m = Matrix.empty(3, 0) + m.row_size.should == 3 + m.column_size.should == 0 + + m = Matrix.empty(0, 3) + m.row_size.should == 0 + m.column_size.should == 3 + end + + it "has arguments defaulting to 0" do + Matrix.empty.should == Matrix.empty(0, 0) + Matrix.empty(42).should == Matrix.empty(42, 0) + end + + it "does not accept more than two parameters" do + ->{ + Matrix.empty(1, 2, 3) + }.should raise_error(ArgumentError) + end + + it "raises an error if both dimensions are > 0" do + ->{ + Matrix.empty(1, 2) + }.should raise_error(ArgumentError) + end + + it "raises an error if any dimension is < 0" do + ->{ + Matrix.empty(-2, 0) + }.should raise_error(ArgumentError) + + ->{ + Matrix.empty(0, -2) + }.should raise_error(ArgumentError) + end + +end + +describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.empty(0, 1).should be_an_instance_of(MatrixSub) + end +end diff --git a/spec/ruby/library/matrix/eql_spec.rb b/spec/ruby/library/matrix/eql_spec.rb new file mode 100644 index 0000000000..ea26c3320d --- /dev/null +++ b/spec/ruby/library/matrix/eql_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/equal_value' +require 'matrix' + +describe "Matrix#eql?" do + it_behaves_like :equal, :eql? + + it "returns false if some elements are == but not eql?" do + Matrix[[1, 2],[3, 4]].eql?(Matrix[[1, 2],[3, 4.0]]).should be_false + end +end diff --git a/spec/ruby/library/matrix/equal_value_spec.rb b/spec/ruby/library/matrix/equal_value_spec.rb new file mode 100644 index 0000000000..10cf1e6c29 --- /dev/null +++ b/spec/ruby/library/matrix/equal_value_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/equal_value' +require 'matrix' + +describe "Matrix#==" do + it_behaves_like :equal, :== + + it "returns true if some elements are == but not eql?" do + Matrix[[1, 2],[3, 4]].should == Matrix[[1, 2],[3, 4.0]] + end +end diff --git a/spec/ruby/library/matrix/exponent_spec.rb b/spec/ruby/library/matrix/exponent_spec.rb new file mode 100644 index 0000000000..38cdfa9276 --- /dev/null +++ b/spec/ruby/library/matrix/exponent_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#**" do + + describe "given an integer _n_" do + it "multiples the Matrix by itself _n_ times" do + m = Matrix[ [7,6], [3,9] ] + (m ** 1).should == m + (m ** 2).should == Matrix[ [67, 96], [48,99] ] + (m ** 2).should == m * m + (m ** 3).should == m * m * m + (m ** 4).should == m * m * m * m + (m ** 5).should == m * m * m * m * m + end + + it "raises a ErrDimensionMismatch for non square matrices" do + m = Matrix[ [1, 1], [1, 2], [2, 3]] + -> { m ** 3 }.should raise_error(Matrix::ErrDimensionMismatch) + -> { m ** 0 }.should raise_error(Matrix::ErrDimensionMismatch) + end + + describe "that is < 0" do + it "returns the inverse of **(-n)" do + m = Matrix[ [1, 1], [1, 2] ] + (m ** -2).should == Matrix[ [5, -3], [-3, 2]] + (m ** -4).should == (m.inverse ** 4) + end + + it "raises a ErrNotRegular for irregular matrices" do + m = Matrix[ [1, 1], [1, 1] ] + -> { m ** -2 }.should raise_error(Matrix::ErrNotRegular) + end + end + + describe "that is 0" do + it "returns the identity for square matrices" do + m = Matrix[ [1, 1], [1, 1] ] + (m ** 0).should == Matrix.identity(2) + end + + it "raises an ErrDimensionMismatch for non-square matrices" do + m = Matrix[ [1, 1] ] + -> { m ** 0 }.should raise_error(Matrix::ErrDimensionMismatch) + end + end + end + + it "returns the power for non integer powers" do + a = Matrix[[5, 4], [4, 5]] + ((a ** 0.5) ** 2).round(8).should == a + a = Matrix[[7, 10], [15, 22]] + ((a ** 0.25) ** 4).round(8).should == a + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + (MatrixSub.ins ** 1).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/find_index_spec.rb b/spec/ruby/library/matrix/find_index_spec.rb new file mode 100644 index 0000000000..c2bfa6d61a --- /dev/null +++ b/spec/ruby/library/matrix/find_index_spec.rb @@ -0,0 +1,146 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#find_index without any argument" do + before :all do + @m = Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ] + end + + it "returns an Enumerator when called without a block" do + enum = @m.find_index + enum.should be_an_instance_of(Enumerator) + enum.to_a.should == [1, 2, 3, 4, 5, 6, 7, 8] + end + + it "returns nil if the block is always false" do + @m.find_index{false}.should be_nil + end + + it "returns the first index for which the block is true" do + @m.find_index{|x| x >= 3}.should == [0, 2] + end +end + +describe "Matrix#find_index with a subselection argument" do + before :all do + @tests = [ + [ Matrix[ [1, 2, 3, 4], [5, 6, 7, 8] ], { + diagonal: [1, 6] , + off_diagonal: [2, 3, 4, 5, 7, 8], + lower: [1, 5, 6] , + strict_lower: [5] , + strict_upper: [2, 3, 4, 7, 8] , + upper: [1, 2, 3, 4, 6, 7, 8] , + } + ], + [ Matrix[ [1, 2], [3, 4], [5, 6], [7, 8] ], { + diagonal: [1, 4] , + off_diagonal: [2, 3, 5, 6, 7, 8], + lower: [1, 3, 4, 5, 6, 7, 8] , + strict_lower: [3, 5, 6, 7, 8] , + strict_upper: [2] , + upper: [1, 2, 4] , + } + ]] + end + + describe "and no generic argument" do + it "returns an Enumerator when called without a block" do + @tests.each do |matrix, h| + h.each do |selector, result| + matrix.find_index(selector).should be_an_instance_of(Enumerator) + end + end + end + + it "yields the rights elements" do + @tests.each do |matrix, h| + h.each do |selector, result| + matrix.find_index(selector).to_a.should == result + end + end + end + + it "returns the first index for which the block returns true" do + @tests.each do |matrix, h| + h.each do |selector, result| + cnt = result.size.div 2 + which = result[cnt] + idx = matrix.find_index(selector){|x| cnt -= 1; x == which} + matrix[*idx].should == which + cnt.should == -1 + end + end + end + + it "returns nil if the block is always false" do + @tests.each do |matrix, h| + h.each do |selector, result| + matrix.find_index(selector){ nil }.should == nil + end + end + end + + end + + describe "and a generic argument" do + it "ignores a block" do + @m.find_index(42, :diagonal){raise "oups"}.should == nil + end + + it "returns the index of the requested value" do + @tests.each do |matrix, h| + h.each do |selector, result| + cnt = result.size / 2 + which = result[cnt] + idx = matrix.find_index(which, selector) + matrix[*idx].should == which + end + end + end + + it "returns nil if the requested value is not found" do + @tests.each do |matrix, h| + h.each do |selector, result| + matrix.find_index(42, selector).should == nil + end + end + end + end + +end + +describe "Matrix#find_index with only a generic argument" do + before :all do + @m = Matrix[ [1, 2, 3, 4], [1, 2, 3, 4] ] + end + + it "returns nil if the value is not found" do + @m.find_index(42).should be_nil + end + + it "returns the first index for of the requested value" do + @m.find_index(3).should == [0, 2] + end + + it "ignores a block" do + @m.find_index(4){raise "oups"}.should == [0, 3] + end +end + +describe "Matrix#find_index with two arguments" do + it "raises an ArgumentError for an unrecognized last argument" do + -> { + @m.find_index(1, "all"){} + }.should raise_error(ArgumentError) + -> { + @m.find_index(1, nil){} + }.should raise_error(ArgumentError) + -> { + @m.find_index(1, :left){} + }.should raise_error(ArgumentError) + -> { + @m.find_index(:diagonal, 1){} + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/matrix/fixtures/classes.rb b/spec/ruby/library/matrix/fixtures/classes.rb new file mode 100644 index 0000000000..394377135f --- /dev/null +++ b/spec/ruby/library/matrix/fixtures/classes.rb @@ -0,0 +1,7 @@ +require 'matrix' + +class MatrixSub < Matrix + def self.ins + rows([[1, 0], [0, 1]]) + end +end diff --git a/spec/ruby/library/matrix/hash_spec.rb b/spec/ruby/library/matrix/hash_spec.rb new file mode 100644 index 0000000000..7dabcd3737 --- /dev/null +++ b/spec/ruby/library/matrix/hash_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#hash" do + + it "returns an Integer" do + Matrix[ [1,2] ].hash.should be_an_instance_of(Integer) + end + + it "returns the same value for the same matrix" do + data = [ [40,5], [2,7] ] + Matrix[ *data ].hash.should == Matrix[ *data ].hash + end + +end diff --git a/spec/ruby/library/matrix/hermitian_spec.rb b/spec/ruby/library/matrix/hermitian_spec.rb new file mode 100644 index 0000000000..177ca64d83 --- /dev/null +++ b/spec/ruby/library/matrix/hermitian_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.hermitian?" do + it "returns true for a hermitian Matrix" do + Matrix[[1, 2, Complex(0, 3)], [2, 4, 5], [Complex(0, -3), 5, 6]].hermitian?.should be_true + end + + it "returns true for a 0x0 empty matrix" do + Matrix.empty.hermitian?.should be_true + end + + it "returns false for an asymmetric Matrix" do + Matrix[[1, 2],[-2, 1]].hermitian?.should be_false + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.hermitian? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end + + it "returns false for a matrix with complex values on the diagonal" do + Matrix[[Complex(1,1)]].hermitian?.should be_false + Matrix[[Complex(1,0)]].hermitian?.should be_true + end +end diff --git a/spec/ruby/library/matrix/identity_spec.rb b/spec/ruby/library/matrix/identity_spec.rb new file mode 100644 index 0000000000..646462bc47 --- /dev/null +++ b/spec/ruby/library/matrix/identity_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/identity' + +describe "Matrix.identity" do + it_behaves_like :matrix_identity, :identity +end diff --git a/spec/ruby/library/matrix/imag_spec.rb b/spec/ruby/library/matrix/imag_spec.rb new file mode 100644 index 0000000000..1c988753d8 --- /dev/null +++ b/spec/ruby/library/matrix/imag_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/imaginary' + +describe "Matrix#imag" do + it_behaves_like :matrix_imaginary, :imag +end diff --git a/spec/ruby/library/matrix/imaginary_spec.rb b/spec/ruby/library/matrix/imaginary_spec.rb new file mode 100644 index 0000000000..ceae4bbe8d --- /dev/null +++ b/spec/ruby/library/matrix/imaginary_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/imaginary' + +describe "Matrix#imaginary" do + it_behaves_like :matrix_imaginary, :imaginary +end diff --git a/spec/ruby/library/matrix/inspect_spec.rb b/spec/ruby/library/matrix/inspect_spec.rb new file mode 100644 index 0000000000..508f478252 --- /dev/null +++ b/spec/ruby/library/matrix/inspect_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#inspect" do + + it "returns a stringified representation of the Matrix" do + Matrix[ [1,2], [2,1] ].inspect.should == "Matrix[[1, 2], [2, 1]]" + end + + it "returns 'Matrix.empty(...)' for empty matrices" do + Matrix[ [], [], [] ].inspect.should == "Matrix.empty(3, 0)" + Matrix.columns([ [], [], [] ]).inspect.should == "Matrix.empty(0, 3)" + end + + it "calls inspect on its contents" do + obj = mock("some_value") + obj.should_receive(:inspect).and_return("some_value") + Matrix[ [1, 2], [3, obj] ].inspect.should == "Matrix[[1, 2], [3, some_value]]" + end + + describe "for a subclass of Matrix" do + it "returns a string using the subclass' name" do + MatrixSub.ins.inspect.should == "MatrixSub[[1, 0], [0, 1]]" + end + end +end diff --git a/spec/ruby/library/matrix/inv_spec.rb b/spec/ruby/library/matrix/inv_spec.rb new file mode 100644 index 0000000000..82879a6d82 --- /dev/null +++ b/spec/ruby/library/matrix/inv_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'shared/inverse' + +describe "Matrix#inv" do + it_behaves_like :inverse, :inv +end diff --git a/spec/ruby/library/matrix/inverse_from_spec.rb b/spec/ruby/library/matrix/inverse_from_spec.rb new file mode 100644 index 0000000000..651d56a244 --- /dev/null +++ b/spec/ruby/library/matrix/inverse_from_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#inverse_from" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/inverse_spec.rb b/spec/ruby/library/matrix/inverse_spec.rb new file mode 100644 index 0000000000..fa3fa7de8a --- /dev/null +++ b/spec/ruby/library/matrix/inverse_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'shared/inverse' + +describe "Matrix#inverse" do + it_behaves_like :inverse, :inverse +end diff --git a/spec/ruby/library/matrix/lower_triangular_spec.rb b/spec/ruby/library/matrix/lower_triangular_spec.rb new file mode 100644 index 0000000000..f3aa4501f4 --- /dev/null +++ b/spec/ruby/library/matrix/lower_triangular_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.lower_triangular?" do + it "returns true for a square lower triangular Matrix" do + Matrix[[1, 0, 0], [1, 2, 0], [1, 2, 3]].lower_triangular?.should be_true + Matrix.diagonal([1, 2, 3]).lower_triangular?.should be_true + Matrix[[1, 0], [1, 2], [1, 2], [1, 2]].lower_triangular?.should be_true + Matrix[[1, 0, 0, 0], [1, 2, 0, 0]].lower_triangular?.should be_true + end + + it "returns true for an empty Matrix" do + Matrix.empty(3, 0).lower_triangular?.should be_true + Matrix.empty(0, 3).lower_triangular?.should be_true + Matrix.empty(0, 0).lower_triangular?.should be_true + end + + it "returns false for a non lower triangular square Matrix" do + Matrix[[0, 1], [0, 0]].lower_triangular?.should be_false + Matrix[[1, 2, 3], [1, 2, 3], [1, 2, 3]].lower_triangular?.should be_false + Matrix[[0, 1], [0, 0], [0, 0], [0, 0]].lower_triangular?.should be_false + Matrix[[0, 0, 0, 1], [0, 0, 0, 0]].lower_triangular?.should be_false + end +end diff --git a/spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb b/spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb new file mode 100644 index 0000000000..9d733066c1 --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#determinant" do + it "returns the determinant when the matrix is square" do + a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]] + a.lup.determinant.should == 15120 # == a.determinant + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[7, 8, 9], [14, 46, 51]], + Matrix[[7, 8], [14, 46], [28, 82]], + ].each do |m| + lup = m.lup + -> { + lup.determinant + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb b/spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb new file mode 100644 index 0000000000..36afb349e6 --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#initialize" do + it "raises an error if argument is not a matrix" do + -> { + Matrix::LUPDecomposition.new([[]]) + }.should raise_error(TypeError) + -> { + Matrix::LUPDecomposition.new(42) + }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/matrix/lup_decomposition/l_spec.rb b/spec/ruby/library/matrix/lup_decomposition/l_spec.rb new file mode 100644 index 0000000000..9514ab5d06 --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/l_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#l" do + before :each do + @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]] + @lu = Matrix::LUPDecomposition.new(@a) + @l = @lu.l + end + + it "returns the first element of to_a" do + @l.should == @lu.to_a[0] + end + + it "returns a lower triangular matrix" do + @l.lower_triangular?.should be_true + end +end diff --git a/spec/ruby/library/matrix/lup_decomposition/p_spec.rb b/spec/ruby/library/matrix/lup_decomposition/p_spec.rb new file mode 100644 index 0000000000..c7b5e9196e --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/p_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#p" do + before :each do + @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]] + @lu = Matrix::LUPDecomposition.new(@a) + @p = @lu.p + end + + it "returns the third element of to_a" do + @p.should == @lu.to_a[2] + end + + it "returns a permutation matrix" do + @p.permutation?.should be_true + end +end diff --git a/spec/ruby/library/matrix/lup_decomposition/solve_spec.rb b/spec/ruby/library/matrix/lup_decomposition/solve_spec.rb new file mode 100644 index 0000000000..66242627e9 --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/solve_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#solve" do + describe "for rectangular matrices" do + it "raises an error for singular matrices" do + a = Matrix[[1, 2, 3], [1, 3, 5], [2, 5, 8]] + lu = Matrix::LUPDecomposition.new(a) + -> { + lu.solve(a) + }.should raise_error(Matrix::ErrNotRegular) + end + + describe "for non singular matrices" do + before :each do + @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]] + @lu = Matrix::LUPDecomposition.new(@a) + end + + it "returns the appropriate empty matrix when given an empty matrix" do + @lu.solve(Matrix.empty(3,0)).should == Matrix.empty(3,0) + empty = Matrix::LUPDecomposition.new(Matrix.empty(0, 0)) + empty.solve(Matrix.empty(0,3)).should == Matrix.empty(0,3) + end + + it "returns the right matrix when given a matrix of the appropriate size" do + solution = Matrix[[1, 2, 3, 4], [0, 1, 2, 3], [-1, -2, -3, -4]] + values = Matrix[[-2, 4, 10, 16], [-37, -28, -19, -10], [-135, -188, -241, -294]] # == @a * solution + @lu.solve(values).should == solution + end + + it "raises an error when given a matrix of the wrong size" do + values = Matrix[[1, 2, 3, 4], [0, 1, 2, 3]] + -> { + @lu.solve(values) + }.should raise_error(Matrix::ErrDimensionMismatch) + end + + it "returns the right vector when given a vector of the appropriate size" do + solution = Vector[1, 2, -1] + values = Vector[14, 55, 29] # == @a * solution + @lu.solve(values).should == solution + end + + it "raises an error when given a vector of the wrong size" do + values = Vector[14, 55] + -> { + @lu.solve(values) + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end + end +end diff --git a/spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb b/spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb new file mode 100644 index 0000000000..9b1dccbbac --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#to_a" do + before :each do + @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]] + @lu = Matrix::LUPDecomposition.new(@a) + @to_a = @lu.to_a + @l, @u, @p = @to_a + end + + it "returns an array of three matrices" do + @to_a.should be_kind_of(Array) + @to_a.length.should == 3 + @to_a.each{|m| m.should be_kind_of(Matrix)} + end + + it "returns [l, u, p] such that l*u == a*p" do + (@l * @u).should == (@p * @a) + end + + it "returns the right values for rectangular matrices" do + [ + Matrix[[7, 8, 9], [14, 46, 51]], + Matrix[[4, 11], [5, 8], [3, 4]], + ].each do |a| + l, u, p = Matrix::LUPDecomposition.new(a).to_a + (l * u).should == (p * a) + end + end + + it "has other properties implied by the specs of #l, #u and #p" +end diff --git a/spec/ruby/library/matrix/lup_decomposition/u_spec.rb b/spec/ruby/library/matrix/lup_decomposition/u_spec.rb new file mode 100644 index 0000000000..ca3dfc1f00 --- /dev/null +++ b/spec/ruby/library/matrix/lup_decomposition/u_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::LUPDecomposition#u" do + before :each do + @a = Matrix[[7, 8, 9], [14, 46, 51], [28, 82, 163]] + @lu = Matrix::LUPDecomposition.new(@a) + @u = @lu.u + end + + it "returns the second element of to_a" do + @u.should == @lu.to_a[1] + end + + it "returns an upper triangular matrix" do + @u.upper_triangular?.should be_true + end +end diff --git a/spec/ruby/library/matrix/map_spec.rb b/spec/ruby/library/matrix/map_spec.rb new file mode 100644 index 0000000000..bc07c48cda --- /dev/null +++ b/spec/ruby/library/matrix/map_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/collect' + +describe "Matrix#map" do + it_behaves_like :collect, :map +end diff --git a/spec/ruby/library/matrix/minor_spec.rb b/spec/ruby/library/matrix/minor_spec.rb new file mode 100644 index 0000000000..009826c3d6 --- /dev/null +++ b/spec/ruby/library/matrix/minor_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#minor" do + before :each do + @matrix = Matrix[ [1,2], [3,4], [5,6] ] + end + + describe "with start_row, nrows, start_col, ncols" do + it "returns the given portion of the Matrix" do + @matrix.minor(0,1,0,2).should == Matrix[ [1, 2] ] + @matrix.minor(1,2,1,1).should == Matrix[ [4], [6] ] + end + + it "returns an empty Matrix if nrows or ncols is 0" do + @matrix.minor(0,0,0,0).should == Matrix[] + @matrix.minor(1,0,1,0).should == Matrix[] + @matrix.minor(1,0,1,1).should == Matrix.columns([[]]) + @matrix.minor(1,1,1,0).should == Matrix[[]] + end + + it "returns nil for out-of-bounds start_row/col" do + r = @matrix.row_size + 1 + c = @matrix.column_size + 1 + @matrix.minor(r,0,0,10).should == nil + @matrix.minor(0,10,c,9).should == nil + @matrix.minor(-r,0,0,10).should == nil + @matrix.minor(0,10,-c,9).should == nil + end + + it "returns nil for negative nrows or ncols" do + @matrix.minor(0,1,0,-1).should == nil + @matrix.minor(0,-1,0,1).should == nil + end + + it "start counting backwards for start_row or start_col below zero" do + @matrix.minor(0, 1, -1, 1).should == @matrix.minor(0, 1, 1, 1) + @matrix.minor(-1, 1, 0, 1).should == @matrix.minor(2, 1, 0, 1) + end + + it "returns empty matrices for extreme start_row/col" do + @matrix.minor(3,10,1,10).should == Matrix.columns([[]]) + @matrix.minor(1,10,2,10).should == Matrix[[], []] + @matrix.minor(3,0,0,10).should == Matrix.columns([[], []]) + end + + it "ignores big nrows or ncols" do + @matrix.minor(0,1,0,20).should == Matrix[ [1, 2] ] + @matrix.minor(1,20,1,1).should == Matrix[ [4], [6] ] + end + end + + describe "with col_range, row_range" do + it "returns the given portion of the Matrix" do + @matrix.minor(0..0, 0..1).should == Matrix[ [1, 2] ] + @matrix.minor(1..2, 1..2).should == Matrix[ [4], [6] ] + @matrix.minor(1...3, 1...3).should == Matrix[ [4], [6] ] + end + + it "returns nil if col_range or row_range is out of range" do + r = @matrix.row_size + 1 + c = @matrix.column_size + 1 + @matrix.minor(r..6, c..6).should == nil + @matrix.minor(0..1, c..6).should == nil + @matrix.minor(r..6, 0..1).should == nil + @matrix.minor(-r..6, -c..6).should == nil + @matrix.minor(0..1, -c..6).should == nil + @matrix.minor(-r..6, 0..1).should == nil + end + + it "start counting backwards for col_range or row_range below zero" do + @matrix.minor(0..1, -2..-1).should == @matrix.minor(0..1, 0..1) + @matrix.minor(0..1, -2..1).should == @matrix.minor(0..1, 0..1) + @matrix.minor(-2..-1, 0..1).should == @matrix.minor(1..2, 0..1) + @matrix.minor(-2..2, 0..1).should == @matrix.minor(1..2, 0..1) + end + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.minor(0, 1, 0, 1).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/minus_spec.rb b/spec/ruby/library/matrix/minus_spec.rb new file mode 100644 index 0000000000..95cf4a6072 --- /dev/null +++ b/spec/ruby/library/matrix/minus_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#-" do + before :each do + @a = Matrix[ [1, 2], [3, 4] ] + @b = Matrix[ [4, 5], [6, 7] ] + end + + it "returns the result of subtracting the corresponding elements of other from self" do + (@a - @b).should == Matrix[ [-3,-3], [-3,-3] ] + end + + it "returns an instance of Matrix" do + (@a - @b).should be_kind_of(Matrix) + end + + it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do + -> { @a - Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch) + end + + it "raises a ExceptionForMatrix::ErrOperationNotDefined if other is a Numeric Type" do + -> { @a - 2 }.should raise_error(Matrix::ErrOperationNotDefined) + -> { @a - 1.2 }.should raise_error(Matrix::ErrOperationNotDefined) + -> { @a - bignum_value }.should raise_error(Matrix::ErrOperationNotDefined) + end + + it "raises a TypeError if other is of wrong type" do + -> { @a - nil }.should raise_error(TypeError) + -> { @a - "a" }.should raise_error(TypeError) + -> { @a - [ [1, 2] ] }.should raise_error(TypeError) + -> { @a - Object.new }.should raise_error(TypeError) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + m = MatrixSub.ins + (m-m).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/multiply_spec.rb b/spec/ruby/library/matrix/multiply_spec.rb new file mode 100644 index 0000000000..206868af92 --- /dev/null +++ b/spec/ruby/library/matrix/multiply_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' + +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#*" do + before :each do + @a = Matrix[ [1, 2], [3, 4] ] + @b = Matrix[ [4, 5], [6, 7] ] + end + + it "returns the result of multiplying the corresponding elements of self and a Matrix" do + (@a * @b).should == Matrix[ [16,19], [36,43] ] + end + + it "returns the result of multiplying the corresponding elements of self and a Vector" do + (@a * Vector[1,2]).should == Vector[5, 11] + end + + it "returns the result of multiplying the elements of self and a Fixnum" do + (@a * 2).should == Matrix[ [2, 4], [6, 8] ] + end + + it "returns the result of multiplying the elements of self and a Bignum" do + (@a * bignum_value).should == Matrix[ + [18446744073709551616, 36893488147419103232], + [55340232221128654848, 73786976294838206464] + ] + end + + it "returns the result of multiplying the elements of self and a Float" do + (@a * 2.0).should == Matrix[ [2.0, 4.0], [6.0, 8.0] ] + end + + it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do + -> { @a * Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch) + end + + it "returns a zero matrix if (nx0) * (0xn)" do + (Matrix[[],[],[]] * Matrix.columns([[],[],[]])).should == Matrix.zero(3) + end + + it "returns an empty matrix if (0xn) * (nx0)" do + (Matrix.columns([[],[],[]]) * Matrix[[],[],[]]).should == Matrix[] + end + + it "returns a mx0 matrix if (mxn) * (nx0)" do + (Matrix[[1,2],[3,4],[5,6]] * Matrix[[],[]]).should == Matrix[[],[],[]] + end + + it "returns a 0xm matrix if (0xm) * (mxn)" do + (Matrix.columns([[], [], []]) * Matrix[[1,2],[3,4],[5,6]]).should == Matrix.columns([[],[]]) + end + + it "raises a TypeError if other is of wrong type" do + -> { @a * nil }.should raise_error(TypeError) + -> { @a * "a" }.should raise_error(TypeError) + -> { @a * [ [1, 2] ] }.should raise_error(TypeError) + -> { @a * Object.new }.should raise_error(TypeError) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + m = MatrixSub.ins + (m*m).should be_an_instance_of(MatrixSub) + (m*1).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/new_spec.rb b/spec/ruby/library/matrix/new_spec.rb new file mode 100644 index 0000000000..3005066846 --- /dev/null +++ b/spec/ruby/library/matrix/new_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.new" do + it "is private" do + Matrix.should have_private_method(:new) + end +end diff --git a/spec/ruby/library/matrix/normal_spec.rb b/spec/ruby/library/matrix/normal_spec.rb new file mode 100644 index 0000000000..a9e6c645fa --- /dev/null +++ b/spec/ruby/library/matrix/normal_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.normal?" do + # it "returns false for non normal matrices" do + # Matrix[[0, 1], [1, 2]].should_not.normal? + # end + + it "returns true for normal matrices" do + Matrix[[1, 1, 0], [0, 1, 1], [1, 0, 1]].should.normal? + Matrix[[0, Complex(0, 2)], [Complex(0, -2), 0]].should.normal? + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.normal? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/orthogonal_spec.rb b/spec/ruby/library/matrix/orthogonal_spec.rb new file mode 100644 index 0000000000..26afe89ff0 --- /dev/null +++ b/spec/ruby/library/matrix/orthogonal_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.orthogonal?" do + it "returns false for non orthogonal matrices" do + Matrix[[0, 1], [1, 2]].should_not.orthogonal? + Matrix[[1, 1, 0], [0, 1, 1], [1, 0, 1]].should_not.orthogonal? + end + + it "returns true for orthogonal matrices" do + Matrix[[0, 1], [1, 0]].should.orthogonal? + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.orthogonal? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/permutation_spec.rb b/spec/ruby/library/matrix/permutation_spec.rb new file mode 100644 index 0000000000..825a9d982c --- /dev/null +++ b/spec/ruby/library/matrix/permutation_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#permutation?" do + it "returns true for a permutation Matrix" do + Matrix[[0, 1, 0], [0, 0, 1], [1, 0, 0]].permutation?.should be_true + end + + it "returns false for a non permutation square Matrix" do + Matrix[[0, 1], [0, 0]].permutation?.should be_false + Matrix[[-1, 0], [0, -1]].permutation?.should be_false + Matrix[[1, 0], [1, 0]].permutation?.should be_false + Matrix[[1, 0], [1, 1]].permutation?.should be_false + end + + it "returns true for an empty 0x0 matrix" do + Matrix.empty(0,0).permutation?.should be_true + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.permutation? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/plus_spec.rb b/spec/ruby/library/matrix/plus_spec.rb new file mode 100644 index 0000000000..2706bad060 --- /dev/null +++ b/spec/ruby/library/matrix/plus_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#+" do + before :each do + @a = Matrix[ [1,2], [3,4] ] + @b = Matrix[ [4,5], [6,7] ] + end + + it "returns the result of adding the corresponding elements of self and other" do + (@a + @b).should == Matrix[ [5,7], [9,11] ] + end + + it "returns an instance of Matrix" do + (@a + @b).should be_kind_of(Matrix) + end + + it "raises a Matrix::ErrDimensionMismatch if the matrices are different sizes" do + -> { @a + Matrix[ [1] ] }.should raise_error(Matrix::ErrDimensionMismatch) + end + + it "raises a ExceptionForMatrix::ErrOperationNotDefined if other is a Numeric Type" do + -> { @a + 2 }.should raise_error(ExceptionForMatrix::ErrOperationNotDefined) + -> { @a + 1.2 }.should raise_error(ExceptionForMatrix::ErrOperationNotDefined) + -> { @a + bignum_value }.should raise_error(ExceptionForMatrix::ErrOperationNotDefined) + end + + it "raises a TypeError if other is of wrong type" do + -> { @a + nil }.should raise_error(TypeError) + -> { @a + "a" }.should raise_error(TypeError) + -> { @a + [ [1, 2] ] }.should raise_error(TypeError) + -> { @a + Object.new }.should raise_error(TypeError) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + m = MatrixSub.ins + (m+m).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/rank_spec.rb b/spec/ruby/library/matrix/rank_spec.rb new file mode 100644 index 0000000000..d4403d23ed --- /dev/null +++ b/spec/ruby/library/matrix/rank_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#rank" do + it "returns the rank of the Matrix" do + Matrix[ [7,6], [3,9] ].rank.should == 2 + end + + it "doesn't loop forever" do + Matrix[ [1,2,3], [4,5,6], [7,8,9] ].rank.should == 2 + Matrix[ [1, 2, 0, 3], [1, -2, 3, 0], [0, 0, 4, 8], [2, 4, 0, 6] ].rank. + should == 3 + end + + it "works for some easy rectangular matrices" do + Matrix[[0,0],[0,0],[1,0]].rank.should == 1 + Matrix[[0,1],[0,0],[1,0]].rank.should == 2 + end +end diff --git a/spec/ruby/library/matrix/real_spec.rb b/spec/ruby/library/matrix/real_spec.rb new file mode 100644 index 0000000000..38033c63c8 --- /dev/null +++ b/spec/ruby/library/matrix/real_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#real?" do + it "returns true for matrices with all real entries" do + Matrix[ [1, 2], [3, 4] ].real?.should be_true + Matrix[ [1.9, 2], [3, 4] ].real?.should be_true + end + + it "returns true for empty matrices" do + Matrix.empty.real?.should be_true + end + + it "returns false if one element is a Complex" do + Matrix[ [Complex(1,1), 2], [3, 4] ].real?.should be_false + end + + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns false if one element is a Complex whose imaginary part is 0" do + Matrix[ [Complex(1,0), 2], [3, 4] ].real?.should be_false + end + end +end + +describe "Matrix#real" do + it "returns a matrix with the real part of the elements of the receiver" do + Matrix[ [1, 2], [3, 4] ].real.should == Matrix[ [1, 2], [3, 4] ] + Matrix[ [1.9, Complex(1,1)], [Complex(-0.42, 0), 4] ].real.should == Matrix[ [1.9, 1], [-0.42, 4] ] + end + + it "returns empty matrices on the same size if empty" do + Matrix.empty(0, 3).real.should == Matrix.empty(0, 3) + Matrix.empty(3, 0).real.should == Matrix.empty(3, 0) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.real.should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/rect_spec.rb b/spec/ruby/library/matrix/rect_spec.rb new file mode 100644 index 0000000000..83a0404e47 --- /dev/null +++ b/spec/ruby/library/matrix/rect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/rectangular' + +describe "Matrix#rect" do + it_behaves_like :matrix_rectangular, :rect +end diff --git a/spec/ruby/library/matrix/rectangular_spec.rb b/spec/ruby/library/matrix/rectangular_spec.rb new file mode 100644 index 0000000000..a235fac640 --- /dev/null +++ b/spec/ruby/library/matrix/rectangular_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/rectangular' + +describe "Matrix#rectangular" do + it_behaves_like :matrix_rectangular, :rectangular +end diff --git a/spec/ruby/library/matrix/regular_spec.rb b/spec/ruby/library/matrix/regular_spec.rb new file mode 100644 index 0000000000..3699d0ef8b --- /dev/null +++ b/spec/ruby/library/matrix/regular_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#regular?" do + + it "returns false for singular matrices" do + m = Matrix[ [1,2,3], [3,4,3], [0,0,0] ] + m.regular?.should be_false + + m = Matrix[ [1,2,9], [3,4,9], [1,2,9] ] + m.regular?.should be_false + end + + it "returns true if the Matrix is regular" do + Matrix[ [0,1], [1,0] ].regular?.should be_true + end + + it "returns true for an empty 0x0 matrix" do + Matrix.empty(0,0).regular?.should be_true + end + + it "raises an error for rectangular matrices" do + -> { + Matrix[[1], [2], [3]].regular? + }.should raise_error(Matrix::ErrDimensionMismatch) + + -> { + Matrix.empty(3,0).regular? + }.should raise_error(Matrix::ErrDimensionMismatch) + end +end diff --git a/spec/ruby/library/matrix/round_spec.rb b/spec/ruby/library/matrix/round_spec.rb new file mode 100644 index 0000000000..1dc29df890 --- /dev/null +++ b/spec/ruby/library/matrix/round_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix#round" do + it "returns a matrix with all entries rounded" do + Matrix[ [1, 2.34], [5.67, 8] ].round.should == Matrix[ [1, 2], [6, 8] ] + Matrix[ [1, 2.34], [5.67, 8] ].round(1).should == Matrix[ [1, 2.3], [5.7, 8] ] + end + + it "returns empty matrices on the same size if empty" do + Matrix.empty(0, 3).round.should == Matrix.empty(0, 3) + Matrix.empty(3, 0).round(42).should == Matrix.empty(3, 0) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.round.should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/row_size_spec.rb b/spec/ruby/library/matrix/row_size_spec.rb new file mode 100644 index 0000000000..eb3aef2e2f --- /dev/null +++ b/spec/ruby/library/matrix/row_size_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#row_size" do + it "returns the number rows" do + Matrix[ [1,2], [3, 4], [5, 6] ].row_size.should == 3 + end + + it "returns the number rows even for some empty matrices" do + Matrix[ [], [], [] ].row_size.should == 3 + end + +end diff --git a/spec/ruby/library/matrix/row_spec.rb b/spec/ruby/library/matrix/row_spec.rb new file mode 100644 index 0000000000..00b1f02a8e --- /dev/null +++ b/spec/ruby/library/matrix/row_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#row" do + before :all do + @m = Matrix[ [1, 2], [2, 3], [3, 4] ] + end + + it "returns a Vector when called without a block" do + @m.row(0).should == Vector[1,2] + end + + it "yields the elements of the row when called with a block" do + a = [] + @m.row(0) {|x| a << x} + a.should == [1,2] + end + + it "counts backwards for negative argument" do + @m.row(-1).should == Vector[3, 4] + end + + it "returns self when called with a block" do + @m.row(0) { |x| x }.should equal(@m) + end + + it "returns nil when out of bounds" do + @m.row(3).should == nil + @m.row(-4).should == nil + end + + it "never yields when out of bounds" do + -> { @m.row(3){ raise } }.should_not raise_error + -> { @m.row(-4){ raise } }.should_not raise_error + end +end diff --git a/spec/ruby/library/matrix/row_vector_spec.rb b/spec/ruby/library/matrix/row_vector_spec.rb new file mode 100644 index 0000000000..341437ee05 --- /dev/null +++ b/spec/ruby/library/matrix/row_vector_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.row_vector" do + + it "returns a Matrix" do + Matrix.row_vector([]).should be_an_instance_of(Matrix) + end + + it "returns a single-row Matrix with the specified values" do + Matrix.row_vector([1,2]).should == Matrix[ [1,2] ] + end + + it "returns a 1x0 matrix when called with an empty Array" do + Matrix.row_vector([]).should == Matrix[ [] ] + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.row_vector([1]).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/row_vectors_spec.rb b/spec/ruby/library/matrix/row_vectors_spec.rb new file mode 100644 index 0000000000..6f99c439a6 --- /dev/null +++ b/spec/ruby/library/matrix/row_vectors_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#row_vectors" do + + before :each do + @vectors = Matrix[ [1,2], [3,4] ].row_vectors + end + + it "returns an Array" do + Matrix[ [1,2], [3,4] ].row_vectors.should be_an_instance_of(Array) + end + + it "returns an Array of Vectors" do + @vectors.all? {|v| v.should be_an_instance_of(Vector)} + end + + it "returns each row as a Vector" do + @vectors.should == [Vector[1,2], Vector[3,4]] + end + + it "returns an empty Array for empty matrices" do + Matrix[].row_vectors.should == [] + Matrix[ [] ].row_vectors.should == [ Vector[] ] + end +end diff --git a/spec/ruby/library/matrix/rows_spec.rb b/spec/ruby/library/matrix/rows_spec.rb new file mode 100644 index 0000000000..41ba635775 --- /dev/null +++ b/spec/ruby/library/matrix/rows_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.rows" do + before :each do + @a = [1, 2] + @b = [3, 4] + @m = Matrix.rows([@a, @b]) + end + + it "returns a Matrix" do + @m.should be_kind_of(Matrix) + end + + it "creates a matrix from argument rows" do + @m.row(0).to_a.should == @a + @m.row(1).to_a.should == @b + end + + it "copies the original rows by default" do + @a << 3 + @b << 6 + @m.row(0).should_not equal(@a) + @m.row(1).should_not equal(@b) + end + + it "references the original rows if copy is false" do + @m_ref = Matrix.rows([@a, @b], false) + @a << 3 + @b << 6 + @m_ref.row(0).to_a.should == @a + @m_ref.row(1).to_a.should == @b + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.rows([[0, 1], [0, 1]]).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/scalar/Fail_spec.rb b/spec/ruby/library/matrix/scalar/Fail_spec.rb new file mode 100644 index 0000000000..9d8f9bd5e8 --- /dev/null +++ b/spec/ruby/library/matrix/scalar/Fail_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#Fail" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/Raise_spec.rb b/spec/ruby/library/matrix/scalar/Raise_spec.rb new file mode 100644 index 0000000000..27e11c1f77 --- /dev/null +++ b/spec/ruby/library/matrix/scalar/Raise_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#Raise" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/divide_spec.rb b/spec/ruby/library/matrix/scalar/divide_spec.rb new file mode 100644 index 0000000000..5d726943fe --- /dev/null +++ b/spec/ruby/library/matrix/scalar/divide_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#/" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/exponent_spec.rb b/spec/ruby/library/matrix/scalar/exponent_spec.rb new file mode 100644 index 0000000000..8e9ef52ff2 --- /dev/null +++ b/spec/ruby/library/matrix/scalar/exponent_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#**" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/included_spec.rb b/spec/ruby/library/matrix/scalar/included_spec.rb new file mode 100644 index 0000000000..cb3beb2ecd --- /dev/null +++ b/spec/ruby/library/matrix/scalar/included_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar.included" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/initialize_spec.rb b/spec/ruby/library/matrix/scalar/initialize_spec.rb new file mode 100644 index 0000000000..23145ad0de --- /dev/null +++ b/spec/ruby/library/matrix/scalar/initialize_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#initialize" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/minus_spec.rb b/spec/ruby/library/matrix/scalar/minus_spec.rb new file mode 100644 index 0000000000..c727ea1659 --- /dev/null +++ b/spec/ruby/library/matrix/scalar/minus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#-" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/multiply_spec.rb b/spec/ruby/library/matrix/scalar/multiply_spec.rb new file mode 100644 index 0000000000..1a2a83d83e --- /dev/null +++ b/spec/ruby/library/matrix/scalar/multiply_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#*" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar/plus_spec.rb b/spec/ruby/library/matrix/scalar/plus_spec.rb new file mode 100644 index 0000000000..c94689a702 --- /dev/null +++ b/spec/ruby/library/matrix/scalar/plus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Matrix::Scalar#+" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/scalar_spec.rb b/spec/ruby/library/matrix/scalar_spec.rb new file mode 100644 index 0000000000..7fdd64c9d9 --- /dev/null +++ b/spec/ruby/library/matrix/scalar_spec.rb @@ -0,0 +1,67 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.scalar" do + + before :each do + @side = 3 + @value = 8 + @a = Matrix.scalar(@side, @value) + end + + it "returns a Matrix" do + @a.should be_kind_of(Matrix) + end + + it "returns a n x n matrix" do + @a.row_size.should == @side + @a.column_size.should == @side + end + + it "initializes diagonal to value" do + (0...@a.row_size).each do |i| + @a[i, i].should == @value + end + end + + it "initializes all non-diagonal values to 0" do + (0...@a.row_size).each do |i| + (0...@a.column_size).each do |j| + if i != j + @a[i, j].should == 0 + end + end + end + end + + before :each do + @side = 3 + @value = 8 + @a = Matrix.scalar(@side, @value) + end + + it "returns a Matrix" do + @a.should be_kind_of(Matrix) + end + + it "returns a square matrix, where the first argument specifies the side of the square" do + @a.row_size.should == @side + @a.column_size.should == @side + end + + it "puts the second argument in all diagonal values" do + (0...@a.row_size).each do |i| + @a[i, i].should == @value + end + end + + it "fills all values not on the main diagonal with 0" do + (0...@a.row_size).each do |i| + (0...@a.column_size).each do |j| + if i != j + @a[i, j].should == 0 + end + end + end + end +end diff --git a/spec/ruby/library/matrix/shared/collect.rb b/spec/ruby/library/matrix/shared/collect.rb new file mode 100644 index 0000000000..852f7fd6cf --- /dev/null +++ b/spec/ruby/library/matrix/shared/collect.rb @@ -0,0 +1,26 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :collect, shared: true do + before :all do + @m = Matrix[ [1, 2], [1, 2] ] + end + + it "returns an instance of Matrix" do + @m.send(@method){|n| n * 2 }.should be_kind_of(Matrix) + end + + it "returns a Matrix where each element is the result of the block" do + @m.send(@method) { |n| n * 2 }.should == Matrix[ [2, 4], [2, 4] ] + end + + it "returns an enumerator if no block is given" do + @m.send(@method).should be_an_instance_of(Enumerator) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.send(@method){1}.should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/shared/conjugate.rb b/spec/ruby/library/matrix/shared/conjugate.rb new file mode 100644 index 0000000000..d87658f855 --- /dev/null +++ b/spec/ruby/library/matrix/shared/conjugate.rb @@ -0,0 +1,20 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :matrix_conjugate, shared: true do + it "returns a matrix with all entries 'conjugated'" do + Matrix[ [1, 2], [3, 4] ].send(@method).should == Matrix[ [1, 2], [3, 4] ] + Matrix[ [1.9, Complex(1,1)], [3, 4] ].send(@method).should == Matrix[ [1.9, Complex(1,-1)], [3, 4] ] + end + + it "returns empty matrices on the same size if empty" do + Matrix.empty(0, 3).send(@method).should == Matrix.empty(0, 3) + Matrix.empty(3, 0).send(@method).should == Matrix.empty(3, 0) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/shared/determinant.rb b/spec/ruby/library/matrix/shared/determinant.rb new file mode 100644 index 0000000000..9e0528c24b --- /dev/null +++ b/spec/ruby/library/matrix/shared/determinant.rb @@ -0,0 +1,38 @@ +require 'matrix' + +describe :determinant, shared: true do + it "returns the determinant of a square Matrix" do + m = Matrix[ [7,6], [3,9] ] + m.send(@method).should == 45 + + m = Matrix[ [9, 8], [6,5] ] + m.send(@method).should == -3 + + m = Matrix[ [9,8,3], [4,20,5], [1,1,1] ] + m.send(@method).should == 95 + end + + it "returns the determinant of a single-element Matrix" do + m = Matrix[ [2] ] + m.send(@method).should == 2 + end + + it "returns 1 for an empty Matrix" do + m = Matrix[ ] + m.send(@method).should == 1 + end + + it "returns the determinant even for Matrices containing 0 as first entry" do + Matrix[[0,1],[1,0]].send(@method).should == -1 + end + + it "raises an error for rectangular matrices" do + -> { + Matrix[[1], [2], [3]].send(@method) + }.should raise_error(Matrix::ErrDimensionMismatch) + + -> { + Matrix.empty(3,0).send(@method) + }.should raise_error(Matrix::ErrDimensionMismatch) + end +end diff --git a/spec/ruby/library/matrix/shared/equal_value.rb b/spec/ruby/library/matrix/shared/equal_value.rb new file mode 100644 index 0000000000..2b2311d49e --- /dev/null +++ b/spec/ruby/library/matrix/shared/equal_value.rb @@ -0,0 +1,33 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :equal, shared: true do + before do + @matrix = Matrix[ [1, 2, 3, 4, 5], [2, 3, 4, 5, 6] ] + end + + it "returns true for self" do + @matrix.send(@method, @matrix).should be_true + end + + it "returns true for equal matrices" do + @matrix.send(@method, Matrix[ [1, 2, 3, 4, 5], [2, 3, 4, 5, 6] ]).should be_true + end + + it "returns false for different matrices" do + @matrix.send(@method, Matrix[ [42, 2, 3, 4, 5], [2, 3, 4, 5, 6] ]).should be_false + @matrix.send(@method, Matrix[ [1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7] ]).should be_false + @matrix.send(@method, Matrix[ [1, 2, 3], [2, 3, 4] ]).should be_false + end + + it "returns false for different empty matrices" do + Matrix.empty(42, 0).send(@method, Matrix.empty(6, 0)).should be_false + Matrix.empty(0, 42).send(@method, Matrix.empty(0, 6)).should be_false + Matrix.empty(0, 0).send(@method, Matrix.empty(6, 0)).should be_false + Matrix.empty(0, 0).send(@method, Matrix.empty(0, 6)).should be_false + end + + it "doesn't distinguish on subclasses" do + MatrixSub.ins.send(@method, Matrix.I(2)).should be_true + end +end diff --git a/spec/ruby/library/matrix/shared/identity.rb b/spec/ruby/library/matrix/shared/identity.rb new file mode 100644 index 0000000000..114f86e7b0 --- /dev/null +++ b/spec/ruby/library/matrix/shared/identity.rb @@ -0,0 +1,19 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :matrix_identity, shared: true do + it "returns a Matrix" do + Matrix.send(@method, 2).should be_kind_of(Matrix) + end + + it "returns a n x n identity matrix" do + Matrix.send(@method, 3).should == Matrix.scalar(3, 1) + Matrix.send(@method, 100).should == Matrix.scalar(100, 1) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.send(@method, 2).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/shared/imaginary.rb b/spec/ruby/library/matrix/shared/imaginary.rb new file mode 100644 index 0000000000..d28ecc69f1 --- /dev/null +++ b/spec/ruby/library/matrix/shared/imaginary.rb @@ -0,0 +1,20 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :matrix_imaginary, shared: true do + it "returns a matrix with the imaginary part of the elements of the receiver" do + Matrix[ [1, 2], [3, 4] ].send(@method).should == Matrix[ [0, 0], [0, 0] ] + Matrix[ [1.9, Complex(1,1)], [Complex(-2,0.42), 4] ].send(@method).should == Matrix[ [0, 1], [0.42, 0] ] + end + + it "returns empty matrices on the same size if empty" do + Matrix.empty(0, 3).send(@method).should == Matrix.empty(0, 3) + Matrix.empty(3, 0).send(@method).should == Matrix.empty(3, 0) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/shared/inverse.rb b/spec/ruby/library/matrix/shared/inverse.rb new file mode 100644 index 0000000000..c8a6b90da5 --- /dev/null +++ b/spec/ruby/library/matrix/shared/inverse.rb @@ -0,0 +1,38 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :inverse, shared: true do + + it "returns a Matrix" do + Matrix[ [1,2], [2,1] ].send(@method).should be_an_instance_of(Matrix) + end + + it "returns the inverse of the Matrix" do + Matrix[ + [1, 3, 3], [1, 4, 3], [1, 3, 4] + ].send(@method).should == + Matrix[ + [7, -3, -3], [-1, 1, 0], [-1, 0, 1] + ] + end + + it "returns the inverse of the Matrix (other case)" do + Matrix[ + [1, 2, 3], [0, 1, 4], [5, 6, 0] + ].send(@method).should be_close_to_matrix([ + [-24, 18, 5], [20, -15, -4], [-5, 4, 1] + ]) + end + + it "raises a ErrDimensionMismatch if the Matrix is not square" do + ->{ + Matrix[ [1,2,3], [1,2,3] ].send(@method) + }.should raise_error(Matrix::ErrDimensionMismatch) + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/shared/rectangular.rb b/spec/ruby/library/matrix/shared/rectangular.rb new file mode 100644 index 0000000000..3d9a0dfe8a --- /dev/null +++ b/spec/ruby/library/matrix/shared/rectangular.rb @@ -0,0 +1,18 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :matrix_rectangular, shared: true do + it "returns [receiver.real, receiver.imag]" do + m = Matrix[ [1.2, Complex(1,2)], [Complex(-2,0.42), 4] ] + m.send(@method).should == [m.real, m.imag] + + m = Matrix.empty(3, 0) + m.send(@method).should == [m.real, m.imag] + end + + describe "for a subclass of Matrix" do + it "returns instances of that subclass" do + MatrixSub.ins.send(@method).each{|m| m.should be_an_instance_of(MatrixSub) } + end + end +end diff --git a/spec/ruby/library/matrix/shared/trace.rb b/spec/ruby/library/matrix/shared/trace.rb new file mode 100644 index 0000000000..57b89863f8 --- /dev/null +++ b/spec/ruby/library/matrix/shared/trace.rb @@ -0,0 +1,12 @@ +require 'matrix' + +describe :trace, shared: true do + it "returns the sum of diagonal elements in a square Matrix" do + Matrix[[7,6], [3,9]].trace.should == 16 + end + + it "returns the sum of diagonal elements in a rectangular Matrix" do + ->{ Matrix[[1,2,3], [4,5,6]].trace}.should raise_error(Matrix::ErrDimensionMismatch) + end + +end diff --git a/spec/ruby/library/matrix/shared/transpose.rb b/spec/ruby/library/matrix/shared/transpose.rb new file mode 100644 index 0000000000..89b1d025be --- /dev/null +++ b/spec/ruby/library/matrix/shared/transpose.rb @@ -0,0 +1,19 @@ +require_relative '../fixtures/classes' +require 'matrix' + +describe :matrix_transpose, shared: true do + it "returns a transposed matrix" do + Matrix[[1, 2], [3, 4], [5, 6]].send(@method).should == Matrix[[1, 3, 5], [2, 4, 6]] + end + + it "can transpose empty matrices" do + m = Matrix[[], [], []] + m.send(@method).send(@method).should == m + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.ins.send(@method).should be_an_instance_of(MatrixSub) + end + end +end diff --git a/spec/ruby/library/matrix/singular_spec.rb b/spec/ruby/library/matrix/singular_spec.rb new file mode 100644 index 0000000000..7bba36a54a --- /dev/null +++ b/spec/ruby/library/matrix/singular_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#singular?" do + it "returns true for singular matrices" do + m = Matrix[ [1,2,3], [3,4,3], [0,0,0] ] + m.singular?.should be_true + + m = Matrix[ [1,2,9], [3,4,9], [1,2,9] ] + m.singular?.should be_true + end + + it "returns false if the Matrix is regular" do + Matrix[ [0,1], [1,0] ].singular?.should be_false + end + + it "returns false for an empty 0x0 matrix" do + Matrix.empty(0,0).singular?.should be_false + end + + it "raises an error for rectangular matrices" do + -> { + Matrix[[1], [2], [3]].singular? + }.should raise_error(Matrix::ErrDimensionMismatch) + + -> { + Matrix.empty(3,0).singular? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + +end diff --git a/spec/ruby/library/matrix/spec_helper.rb b/spec/ruby/library/matrix/spec_helper.rb new file mode 100644 index 0000000000..d44612981a --- /dev/null +++ b/spec/ruby/library/matrix/spec_helper.rb @@ -0,0 +1,35 @@ +class BeCloseToMatrixMatcher + def initialize(expected, tolerance = TOLERANCE) + SpecExpectation.matcher! rescue "Used with the balance_should_and_match branch of mspec" + @expected = Matrix[*expected] + @tolerance = tolerance + end + + def matches?(actual) + @actual = actual + return false unless @actual.is_a? Matrix + return false unless @actual.row_size == @expected.row_size + @actual.row_size.times do |i| + a, e = @actual.row(i), @expected.row(i) + return false unless a.size == e.size + a.size.times do |j| + return false unless (a[j] - e[j]).abs < @tolerance + end + end + true + end + + def failure_message + ["Expected #{@expected}", "to be within +/- #{@tolerance} of #{@actual}"] + end + + def negative_failure_message + ["Expected #{@expected}", "not to be within +/- #{@tolerance} of #{@actual}"] + end +end + +class Object + def be_close_to_matrix(expected, tolerance = TOLERANCE) + BeCloseToMatrixMatcher.new(expected, tolerance) + end +end diff --git a/spec/ruby/library/matrix/square_spec.rb b/spec/ruby/library/matrix/square_spec.rb new file mode 100644 index 0000000000..25d2d1ad9c --- /dev/null +++ b/spec/ruby/library/matrix/square_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#square?" do + + it "returns true when the Matrix is square" do + Matrix[ [1,2], [2,4] ].square?.should be_true + Matrix[ [100,3,5], [9.5, 4.9, 8], [2,0,77] ].square?.should be_true + end + + it "returns true when the Matrix has only one element" do + Matrix[ [9] ].square?.should be_true + end + + it "returns false when the Matrix is rectangular" do + Matrix[ [1, 2] ].square?.should be_false + end + + it "returns false when the Matrix is rectangular" do + Matrix[ [1], [2] ].square?.should be_false + end + + it "returns handles empty matrices" do + Matrix[].square?.should be_true + Matrix[[]].square?.should be_false + Matrix.columns([[]]).square?.should be_false + end +end diff --git a/spec/ruby/library/matrix/symmetric_spec.rb b/spec/ruby/library/matrix/symmetric_spec.rb new file mode 100644 index 0000000000..6f2a99276a --- /dev/null +++ b/spec/ruby/library/matrix/symmetric_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.symmetric?" do + it "returns true for a symmetric Matrix" do + Matrix[[1, 2, Complex(0, 3)], [2, 4, 5], [Complex(0, 3), 5, 6]].symmetric?.should be_true + end + + it "returns true for a 0x0 empty matrix" do + Matrix.empty.symmetric?.should be_true + end + + it "returns false for an asymmetric Matrix" do + Matrix[[1, 2],[-2, 1]].symmetric?.should be_false + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.symmetric? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/t_spec.rb b/spec/ruby/library/matrix/t_spec.rb new file mode 100644 index 0000000000..6f1a5178e0 --- /dev/null +++ b/spec/ruby/library/matrix/t_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/transpose' + +describe "Matrix#transpose" do + it_behaves_like :matrix_transpose, :t +end diff --git a/spec/ruby/library/matrix/to_a_spec.rb b/spec/ruby/library/matrix/to_a_spec.rb new file mode 100644 index 0000000000..b5d55c5d67 --- /dev/null +++ b/spec/ruby/library/matrix/to_a_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#to_a" do + it "returns the array of arrays that describe the rows of the matrix" do + Matrix[].to_a.should == [] + Matrix[[]].to_a.should == [[]] + Matrix[[1]].to_a.should == [[1]] + Matrix[[1, 2], [3, 4]].to_a.should == [[1, 2],[3, 4]] + end +end diff --git a/spec/ruby/library/matrix/to_s_spec.rb b/spec/ruby/library/matrix/to_s_spec.rb new file mode 100644 index 0000000000..f529fe3bcd --- /dev/null +++ b/spec/ruby/library/matrix/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix#to_s" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/matrix/tr_spec.rb b/spec/ruby/library/matrix/tr_spec.rb new file mode 100644 index 0000000000..e17bd790d7 --- /dev/null +++ b/spec/ruby/library/matrix/tr_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/trace' +require 'matrix' + +describe "Matrix#tr" do + it_behaves_like :trace, :tr +end diff --git a/spec/ruby/library/matrix/trace_spec.rb b/spec/ruby/library/matrix/trace_spec.rb new file mode 100644 index 0000000000..290e7cb1f7 --- /dev/null +++ b/spec/ruby/library/matrix/trace_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/trace' +require 'matrix' + +describe "Matrix#trace" do + it_behaves_like :trace, :trace +end diff --git a/spec/ruby/library/matrix/transpose_spec.rb b/spec/ruby/library/matrix/transpose_spec.rb new file mode 100644 index 0000000000..79600dd439 --- /dev/null +++ b/spec/ruby/library/matrix/transpose_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/transpose' + +describe "Matrix#transpose" do + it_behaves_like :matrix_transpose, :transpose +end diff --git a/spec/ruby/library/matrix/unit_spec.rb b/spec/ruby/library/matrix/unit_spec.rb new file mode 100644 index 0000000000..6a41d729c7 --- /dev/null +++ b/spec/ruby/library/matrix/unit_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/identity' + +describe "Matrix.unit" do + it_behaves_like :matrix_identity, :unit +end diff --git a/spec/ruby/library/matrix/unitary_spec.rb b/spec/ruby/library/matrix/unitary_spec.rb new file mode 100644 index 0000000000..c214ee9b2f --- /dev/null +++ b/spec/ruby/library/matrix/unitary_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' + +require 'matrix' + +describe "Matrix.unitary?" do + it "returns false for non unitary matrices" do + Matrix[[0, 1], [1, 2]].should_not.unitary? + Matrix[[0, Complex(0, 2)], [Complex(0, 2), 0]].should_not.unitary? + Matrix[[1, 1, 0], [0, 1, 1], [1, 0, 1]].should_not.unitary? + end + + it "returns true for unitary matrices" do + Matrix[[0, Complex(0, 1)], [Complex(0, 1), 0]].should.unitary? + end + + it "returns true for unitary matrices with a Complex and a negative #imag" do + Matrix[[0, Complex(0, 1)], [Complex(0, -1), 0]].should.unitary? + end + + it "raises an error for rectangular matrices" do + [ + Matrix[[0], [0]], + Matrix[[0, 0]], + Matrix.empty(0, 2), + Matrix.empty(2, 0), + ].each do |rectangular_matrix| + -> { + rectangular_matrix.unitary? + }.should raise_error(Matrix::ErrDimensionMismatch) + end + end +end diff --git a/spec/ruby/library/matrix/upper_triangular_spec.rb b/spec/ruby/library/matrix/upper_triangular_spec.rb new file mode 100644 index 0000000000..2514294a80 --- /dev/null +++ b/spec/ruby/library/matrix/upper_triangular_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require 'matrix' + +describe "Matrix.upper_triangular?" do + it "returns true for an upper triangular Matrix" do + Matrix[[1, 2, 3], [0, 2, 3], [0, 0, 3]].upper_triangular?.should be_true + Matrix.diagonal([1, 2, 3]).upper_triangular?.should be_true + Matrix[[1, 2], [0, 2], [0, 0], [0, 0]].upper_triangular?.should be_true + Matrix[[1, 2, 3, 4], [0, 2, 3, 4]].upper_triangular?.should be_true + end + + it "returns false for a non upper triangular square Matrix" do + Matrix[[0, 0], [1, 0]].upper_triangular?.should be_false + Matrix[[1, 2, 3], [1, 2, 3], [1, 2, 3]].upper_triangular?.should be_false + Matrix[[0, 0], [0, 0], [0, 0], [0, 1]].upper_triangular?.should be_false + Matrix[[0, 0, 0, 0], [1, 0, 0, 0]].upper_triangular?.should be_false + end + + it "returns true for an empty matrix" do + Matrix.empty(3,0).upper_triangular?.should be_true + Matrix.empty(0,3).upper_triangular?.should be_true + Matrix.empty(0,0).upper_triangular?.should be_true + end +end diff --git a/spec/ruby/library/matrix/vector/cross_product_spec.rb b/spec/ruby/library/matrix/vector/cross_product_spec.rb new file mode 100644 index 0000000000..c2698ade4c --- /dev/null +++ b/spec/ruby/library/matrix/vector/cross_product_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Vector#cross_product" do + it "returns the cross product of a vector" do + Vector[1, 2, 3].cross_product(Vector[0, -4, 5]).should == Vector[22, -5, -4] + end + + it "raises an error unless both vectors have dimension 3" do + -> { + Vector[1, 2, 3].cross_product(Vector[0, -4]) + }.should raise_error(Vector::ErrDimensionMismatch) + end +end diff --git a/spec/ruby/library/matrix/vector/each2_spec.rb b/spec/ruby/library/matrix/vector/each2_spec.rb new file mode 100644 index 0000000000..10d2fc404d --- /dev/null +++ b/spec/ruby/library/matrix/vector/each2_spec.rb @@ -0,0 +1,49 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Vector.each2" do + before :all do + @v = Vector[1, 2, 3] + @v2 = Vector[4, 5, 6] + end + + it "requires one argument" do + -> { @v.each2(@v2, @v2){} }.should raise_error(ArgumentError) + -> { @v.each2(){} }.should raise_error(ArgumentError) + end + + describe "given one argument" do + it "accepts an Array argument" do + a = [] + @v.each2([7, 8, 9]){|x, y| a << x << y} + a.should == [1, 7, 2, 8, 3, 9] + end + + it "raises a DimensionMismatch error if the Vector size is different" do + -> { @v.each2(Vector[1,2]){} }.should raise_error(Vector::ErrDimensionMismatch) + -> { @v.each2(Vector[1,2,3,4]){} }.should raise_error(Vector::ErrDimensionMismatch) + end + + it "yields arguments in sequence" do + a = [] + @v.each2(@v2){|first, second| a << [first, second]} + a.should == [[1, 4], [2, 5], [3, 6]] + end + + it "yield arguments in pairs" do + a = [] + @v.each2(@v2){|*pair| a << pair} + a.should == [[1, 4], [2, 5], [3, 6]] + end + + it "returns self when given a block" do + @v.each2(@v2){}.should equal(@v) + end + + it "returns an enumerator if no block given" do + enum = @v.each2(@v2) + enum.should be_an_instance_of(Enumerator) + enum.to_a.should == [[1, 4], [2, 5], [3, 6]] + end + end +end diff --git a/spec/ruby/library/matrix/vector/eql_spec.rb b/spec/ruby/library/matrix/vector/eql_spec.rb new file mode 100644 index 0000000000..eb2451b550 --- /dev/null +++ b/spec/ruby/library/matrix/vector/eql_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Vector#eql?" do + before do + @vector = Vector[1, 2, 3, 4, 5] + end + + it "returns true for self" do + @vector.eql?(@vector).should be_true + end + + it "returns false when there are a pair corresponding elements which are not equal in the sense of Kernel#eql?" do + @vector.eql?(Vector[1, 2, 3, 4, 5.0]).should be_false + end +end diff --git a/spec/ruby/library/matrix/vector/inner_product_spec.rb b/spec/ruby/library/matrix/vector/inner_product_spec.rb new file mode 100644 index 0000000000..1cf8771e04 --- /dev/null +++ b/spec/ruby/library/matrix/vector/inner_product_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Vector#inner_product" do + it "returns the inner product of a vector" do + Vector[1, 2, 3].inner_product(Vector[0, -4, 5]).should == 7 + end + + it "returns 0 for empty vectors" do + Vector[].inner_product(Vector[]).should == 0 + end + + it "raises an error for mismatched vectors" do + -> { + Vector[1, 2, 3].inner_product(Vector[0, -4]) + }.should raise_error(Vector::ErrDimensionMismatch) + end + + it "uses the conjugate of its argument" do + Vector[Complex(1,2)].inner_product(Vector[Complex(3,4)]).should == Complex(11, 2) + end +end diff --git a/spec/ruby/library/matrix/vector/normalize_spec.rb b/spec/ruby/library/matrix/vector/normalize_spec.rb new file mode 100644 index 0000000000..527c9260de --- /dev/null +++ b/spec/ruby/library/matrix/vector/normalize_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' +require 'matrix' + +describe "Vector#normalize" do + it "returns a normalized copy of the vector" do + x = 0.2672612419124244 + Vector[1, 2, 3].normalize.should == Vector[x, x * 2, x * 3] + end + + it "raises an error for zero vectors" do + -> { + Vector[].normalize + }.should raise_error(Vector::ZeroVectorError) + -> { + Vector[0, 0, 0].normalize + }.should raise_error(Vector::ZeroVectorError) + end +end diff --git a/spec/ruby/library/matrix/zero_spec.rb b/spec/ruby/library/matrix/zero_spec.rb new file mode 100644 index 0000000000..68e8567c26 --- /dev/null +++ b/spec/ruby/library/matrix/zero_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'matrix' + +describe "Matrix.zero" do + it "returns an object of type Matrix" do + Matrix.zero(3).should be_kind_of(Matrix) + end + + it "creates a n x n matrix" do + m3 = Matrix.zero(3) + m3.row_size.should == 3 + m3.column_size.should == 3 + + m8 = Matrix.zero(8) + m8.row_size.should == 8 + m8.column_size.should == 8 + end + + it "initializes all cells to 0" do + size = 10 + m = Matrix.zero(size) + + (0...size).each do |i| + (0...size).each do |j| + m[i, j].should == 0 + end + end + end + + describe "for a subclass of Matrix" do + it "returns an instance of that subclass" do + MatrixSub.zero(3).should be_an_instance_of(MatrixSub) + end + end +end + +describe "Matrix.zero?" do + it "returns true for empty matrices" do + Matrix.empty.should.zero? + Matrix.empty(3,0).should.zero? + Matrix.empty(0,3).should.zero? + end + + it "returns true for matrices with zero entries" do + Matrix.zero(2,3).should.zero? + end + + it "returns false for matrices with non zero entries" do + Matrix[[1]].should_not.zero? + end +end diff --git a/spec/ruby/library/mkmf/mkmf_spec.rb b/spec/ruby/library/mkmf/mkmf_spec.rb new file mode 100644 index 0000000000..18b090e703 --- /dev/null +++ b/spec/ruby/library/mkmf/mkmf_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe 'mkmf' do + it 'can be required with --enable-frozen-string-literal' do + ruby_exe('p MakeMakefile', options: '-rmkmf --enable-frozen-string-literal').should == "MakeMakefile\n" + end +end diff --git a/spec/ruby/library/monitor/enter_spec.rb b/spec/ruby/library/monitor/enter_spec.rb new file mode 100644 index 0000000000..f523c42087 --- /dev/null +++ b/spec/ruby/library/monitor/enter_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "Monitor#enter" do + it "acquires the monitor" do + monitor = Monitor.new + 10.times do + wait_q = Queue.new + continue_q = Queue.new + + thread = Thread.new do + begin + monitor.enter + wait_q << true + continue_q.pop + ensure + monitor.exit + end + end + + wait_q.pop + monitor.mon_locked?.should == true + continue_q << true + thread.join + monitor.mon_locked?.should == false + end + end +end diff --git a/spec/ruby/library/monitor/exit_spec.rb b/spec/ruby/library/monitor/exit_spec.rb new file mode 100644 index 0000000000..952ad9525d --- /dev/null +++ b/spec/ruby/library/monitor/exit_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "Monitor#exit" do + it "raises ThreadError when monitor is not entered" do + m = Monitor.new + + -> { m.exit }.should raise_error(ThreadError) + end +end diff --git a/spec/ruby/library/monitor/mon_initialize_spec.rb b/spec/ruby/library/monitor/mon_initialize_spec.rb new file mode 100644 index 0000000000..e0fe6c2d97 --- /dev/null +++ b/spec/ruby/library/monitor/mon_initialize_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "MonitorMixin#mon_initialize" do + it "can be called in initialize_copy to get a new Mutex and used with synchronize" do + cls = Class.new do + include MonitorMixin + + def initialize(*array) + mon_initialize + @array = array + end + + def to_a + synchronize { @array.dup } + end + + def initialize_copy(other) + mon_initialize + + synchronize do + @array = other.to_a + end + end + end + + instance = cls.new(1, 2, 3) + copy = instance.dup + copy.should_not equal(instance) + end +end diff --git a/spec/ruby/library/monitor/new_cond_spec.rb b/spec/ruby/library/monitor/new_cond_spec.rb new file mode 100644 index 0000000000..ec25d3f8a2 --- /dev/null +++ b/spec/ruby/library/monitor/new_cond_spec.rb @@ -0,0 +1,88 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "Monitor#new_cond" do + it "creates a MonitorMixin::ConditionVariable" do + m = Monitor.new + c = m.new_cond + c.class.should == MonitorMixin::ConditionVariable + end + + it 'returns a condition variable which can be waited on by a thread holding the monitor' do + m = Monitor.new + c = m.new_cond + + 10.times do + + wait_q = Queue.new + thread = Thread.new do + m.synchronize do + wait_q << true + c.wait + end + :done + end + + wait_q.pop + + # Synchronize can't happen until the other thread is waiting. + m.synchronize { c.signal } + + thread.join + thread.value.should == :done + end + end + + it 'returns a condition variable which can be waited on by a thread holding the monitor inside multiple synchronize blocks' do + m = Monitor.new + c = m.new_cond + + 10.times do + + wait_q = Queue.new + thread = Thread.new do + m.synchronize do + m.synchronize do + wait_q << true + c.wait + end + end + :done + end + + wait_q.pop + + #No need to wait here as we cannot synchronize until the other thread is waiting. + m.synchronize { c.signal } + + thread.join + thread.value.should == :done + end + end + + it 'returns a condition variable which can be signalled by a thread holding the monitor inside multiple synchronize blocks' do + m = Monitor.new + c = m.new_cond + + 10.times do + + wait_q = Queue.new + thread = Thread.new do + m.synchronize do + wait_q << true + c.wait + end + :done + end + + wait_q.pop + + # Synchronize can't happen until the other thread is waiting. + m.synchronize { m.synchronize { c.signal } } + + thread.join + thread.value.should == :done + end + end + +end diff --git a/spec/ruby/library/monitor/synchronize_spec.rb b/spec/ruby/library/monitor/synchronize_spec.rb new file mode 100644 index 0000000000..d78393eb3a --- /dev/null +++ b/spec/ruby/library/monitor/synchronize_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "Monitor#synchronize" do + it "unlocks after return, even if it was interrupted by Thread#raise" do + exc_class = Class.new(RuntimeError) + + monitor = Monitor.new + 10.times do + wait_q = Queue.new + continue_q = Queue.new + + thread = Thread.new do + begin + monitor.synchronize do + wait_q << true + # Do not wait here, we are trying to interrupt the ensure part of #synchronize + end + continue_q.pop + rescue exc_class + monitor.should_not.mon_locked? + :ok + end + end + + wait_q.pop + thread.raise exc_class, "interrupt" + continue_q << true + thread.value.should == :ok + end + end + + it "raises a LocalJumpError if not passed a block" do + -> { Monitor.new.synchronize }.should raise_error(LocalJumpError) + end + + it "raises a thread error if the monitor is not owned on exiting the block" do + monitor = Monitor.new + -> { monitor.synchronize { monitor.exit } }.should raise_error(ThreadError) + end +end diff --git a/spec/ruby/library/monitor/try_enter_spec.rb b/spec/ruby/library/monitor/try_enter_spec.rb new file mode 100644 index 0000000000..04b878f720 --- /dev/null +++ b/spec/ruby/library/monitor/try_enter_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "Monitor#try_enter" do + it "will acquire a monitor not held by another thread" do + monitor = Monitor.new + 10.times do + + thread = Thread.new do + val = monitor.try_enter + monitor.exit if val + val + end + + thread.join + thread.value.should == true + end + end + + it "will not acquire a monitor already held by another thread" do + monitor = Monitor.new + 10.times do + monitor.enter + begin + thread = Thread.new do + val = monitor.try_enter + monitor.exit if val + val + end + + thread.join + thread.value.should == false + ensure + monitor.exit + end + monitor.mon_locked?.should == false + end + end +end diff --git a/spec/ruby/library/net-ftp/FTPError_spec.rb b/spec/ruby/library/net-ftp/FTPError_spec.rb new file mode 100644 index 0000000000..0c31b65dcc --- /dev/null +++ b/spec/ruby/library/net-ftp/FTPError_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'net/ftp' + +describe "Net::FTPError" do + it "is an Exception" do + Net::FTPError.should < Exception + end +end diff --git a/spec/ruby/library/net-ftp/FTPPermError_spec.rb b/spec/ruby/library/net-ftp/FTPPermError_spec.rb new file mode 100644 index 0000000000..b43e12c503 --- /dev/null +++ b/spec/ruby/library/net-ftp/FTPPermError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/ftp' + +describe "Net::FTPPermError" do + it "is an Exception" do + Net::FTPPermError.should < Exception + end + + it "is a subclass of Net::FTPError" do + Net::FTPPermError.should < Net::FTPError + end +end diff --git a/spec/ruby/library/net-ftp/FTPProtoError_spec.rb b/spec/ruby/library/net-ftp/FTPProtoError_spec.rb new file mode 100644 index 0000000000..e7abbc0dd8 --- /dev/null +++ b/spec/ruby/library/net-ftp/FTPProtoError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/ftp' + +describe "Net::FTPProtoError" do + it "is an Exception" do + Net::FTPProtoError.should < Exception + end + + it "is a subclass of Net::FTPError" do + Net::FTPPermError.should < Net::FTPError + end +end diff --git a/spec/ruby/library/net-ftp/FTPReplyError_spec.rb b/spec/ruby/library/net-ftp/FTPReplyError_spec.rb new file mode 100644 index 0000000000..fcc7501fc1 --- /dev/null +++ b/spec/ruby/library/net-ftp/FTPReplyError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/ftp' + +describe "Net::FTPReplyError" do + it "is an Exception" do + Net::FTPReplyError.should < Exception + end + + it "is a subclass of Net::FTPError" do + Net::FTPPermError.should < Net::FTPError + end +end diff --git a/spec/ruby/library/net-ftp/FTPTempError_spec.rb b/spec/ruby/library/net-ftp/FTPTempError_spec.rb new file mode 100644 index 0000000000..f4b045dfb5 --- /dev/null +++ b/spec/ruby/library/net-ftp/FTPTempError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/ftp' + +describe "Net::FTPTempError" do + it "is an Exception" do + Net::FTPTempError.should < Exception + end + + it "is a subclass of Net::FTPError" do + Net::FTPPermError.should < Net::FTPError + end +end diff --git a/spec/ruby/library/net-ftp/abort_spec.rb b/spec/ruby/library/net-ftp/abort_spec.rb new file mode 100644 index 0000000000..335d056512 --- /dev/null +++ b/spec/ruby/library/net-ftp/abort_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#abort" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the ABOR command to the server" do + -> { @ftp.abort }.should_not raise_error + end + + it "ignores the response" do + @ftp.abort + @ftp.last_response.should == "220 Dummy FTP Server ready!\n" + end + + it "returns the full response" do + @ftp.abort.should == "226 Closing data connection. (ABOR)\n" + end + + it "does not raise any error when the response code is 225" do + @server.should_receive(:abor).and_respond("225 Data connection open; no transfer in progress.") + -> { @ftp.abort }.should_not raise_error + end + + it "does not raise any error when the response code is 226" do + @server.should_receive(:abor).and_respond("226 Closing data connection.") + -> { @ftp.abort }.should_not raise_error + end + + it "raises a Net::FTPProtoError when the response code is 500" do + @server.should_receive(:abor).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.abort }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPProtoError when the response code is 501" do + @server.should_receive(:abor).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.abort }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPProtoError when the response code is 502" do + @server.should_receive(:abor).and_respond("502 Command not implemented.") + -> { @ftp.abort }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPProtoError when the response code is 421" do + @server.should_receive(:abor).and_respond("421 Service not available, closing control connection.") + -> { @ftp.abort }.should raise_error(Net::FTPProtoError) + end +end diff --git a/spec/ruby/library/net-ftp/acct_spec.rb b/spec/ruby/library/net-ftp/acct_spec.rb new file mode 100644 index 0000000000..ab093448a2 --- /dev/null +++ b/spec/ruby/library/net-ftp/acct_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#acct" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "writes the ACCT command to the server" do + @ftp.acct("my_account") + @ftp.last_response.should == "230 User 'my_account' logged in, proceed. (ACCT)\n" + end + + it "returns nil" do + @ftp.acct("my_account").should == nil + end + + it "does not raise any error when the response code is 230" do + @server.should_receive(:acct).and_respond("230 User logged in, proceed.") + -> { @ftp.acct("my_account") }.should_not raise_error + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:acct).and_respond("530 Not logged in.") + -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:acct).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:acct).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 503" do + @server.should_receive(:acct).and_respond("503 Bad sequence of commands.") + -> { @ftp.acct("my_account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:acct).and_respond("421 Service not available, closing control connection.") + -> { @ftp.acct("my_account") }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/binary_spec.rb b/spec/ruby/library/net-ftp/binary_spec.rb new file mode 100644 index 0000000000..1e0585b795 --- /dev/null +++ b/spec/ruby/library/net-ftp/binary_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#binary" do + it "returns true when self is in binary mode" do + ftp = Net::FTP.new + ftp.binary.should be_true + + ftp.binary = false + ftp.binary.should be_false + end +end + +describe "Net::FTP#binary=" do + it "sets self to binary mode when passed true" do + ftp = Net::FTP.new + + ftp.binary = true + ftp.binary.should be_true + + ftp.binary = false + ftp.binary.should be_false + end +end diff --git a/spec/ruby/library/net-ftp/chdir_spec.rb b/spec/ruby/library/net-ftp/chdir_spec.rb new file mode 100644 index 0000000000..cc129b5e42 --- /dev/null +++ b/spec/ruby/library/net-ftp/chdir_spec.rb @@ -0,0 +1,99 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#chdir" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + describe "when switching to the parent directory" do + it "sends the 'CDUP' command to the server" do + @ftp.chdir("..") + @ftp.last_response.should == "200 Command okay. (CDUP)\n" + end + + it "returns nil" do + @ftp.chdir("..").should be_nil + end + + it "does not raise a Net::FTPPermError when the response code is 500" do + @server.should_receive(:cdup).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.chdir("..") }.should_not raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:cdup).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:cdup).and_respond("502 Command not implemented.") + -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:cdup).and_respond("421 Service not available, closing control connection.") + -> { @ftp.chdir("..") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:cdup).and_respond("530 Not logged in.") + -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:cdup).and_respond("550 Requested action not taken.") + -> { @ftp.chdir("..") }.should raise_error(Net::FTPPermError) + end + end + + it "writes the 'CWD' command with the passed directory to the socket" do + @ftp.chdir("test") + @ftp.last_response.should == "200 Command okay. (CWD test)\n" + end + + it "returns nil" do + @ftp.chdir("test").should be_nil + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:cwd).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:cwd).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:cwd).and_respond("502 Command not implemented.") + -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:cwd).and_respond("421 Service not available, closing control connection.") + -> { @ftp.chdir("test") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:cwd).and_respond("530 Not logged in.") + -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:cwd).and_respond("550 Requested action not taken.") + -> { @ftp.chdir("test") }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/close_spec.rb b/spec/ruby/library/net-ftp/close_spec.rb new file mode 100644 index 0000000000..183f14a84b --- /dev/null +++ b/spec/ruby/library/net-ftp/close_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#close" do + before :each do + @socket = mock("Socket") + @socket.stub!(:closed?).and_return(false) + @socket.stub!(:read_timeout).and_return(60) + @socket.stub!(:read_timeout=).and_return(3) + + @ftp = Net::FTP.new + @ftp.instance_variable_set(:@sock, @socket) + end + + it "closes the socket" do + @socket.should_receive(:close) + @ftp.close.should be_nil + end + + it "does not try to close the socket if it has already been closed" do + @socket.should_receive(:closed?).and_return(true) + @socket.should_not_receive(:close) + @ftp.close.should be_nil + end + + it "does not try to close the socket if it is nil" do + @ftp.instance_variable_set(:@sock, nil) + @ftp.close.should be_nil + end +end diff --git a/spec/ruby/library/net-ftp/closed_spec.rb b/spec/ruby/library/net-ftp/closed_spec.rb new file mode 100644 index 0000000000..84001cdc0f --- /dev/null +++ b/spec/ruby/library/net-ftp/closed_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#closed?" do + before :each do + @socket = mock("Socket") + + @ftp = Net::FTP.new + @ftp.instance_variable_set(:@sock, @socket) + end + + it "returns true when the socket is closed" do + @socket.should_receive(:closed?).and_return(true) + @ftp.closed?.should be_true + end + + it "returns true when the socket is nil" do + @ftp.instance_variable_set(:@sock, nil) + @ftp.closed?.should be_true + end +end diff --git a/spec/ruby/library/net-ftp/connect_spec.rb b/spec/ruby/library/net-ftp/connect_spec.rb new file mode 100644 index 0000000000..e606b11e2a --- /dev/null +++ b/spec/ruby/library/net-ftp/connect_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +# TODO: Add specs for using the SOCKSSocket +describe "Net::FTP#connect" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + end + + after :each do + @server.connect_message = nil + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "tries to connect to the FTP Server on the given host and port" do + -> { @ftp.connect(@server.hostname, @server.server_port) }.should_not raise_error + end + + it "returns nil" do + @ftp.connect(@server.hostname, @server.server_port).should be_nil + end + + it "does not raise any error when the response code is 220" do + @server.connect_message = "220 Dummy FTP Server ready!" + -> { @ftp.connect(@server.hostname, @server.server_port) }.should_not raise_error + end + + it "raises a Net::FTPReplyError when the response code is 120" do + @server.connect_message = "120 Service ready in nnn minutes." + -> { @ftp.connect(@server.hostname, @server.server_port) }.should raise_error(Net::FTPReplyError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.connect_message = "421 Service not available, closing control connection." + -> { @ftp.connect(@server.hostname, @server.server_port) }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/debug_mode_spec.rb b/spec/ruby/library/net-ftp/debug_mode_spec.rb new file mode 100644 index 0000000000..f2ef53c089 --- /dev/null +++ b/spec/ruby/library/net-ftp/debug_mode_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#debug_mode" do + it "returns true when self is in debug mode" do + ftp = Net::FTP.new + ftp.debug_mode.should be_false + + ftp.debug_mode = true + ftp.debug_mode.should be_true + end +end + +describe "Net::FTP#debug_mode=" do + it "sets self into debug mode when passed true" do + ftp = Net::FTP.new + ftp.debug_mode = true + ftp.debug_mode.should be_true + + ftp.debug_mode = false + ftp.debug_mode.should be_false + end +end diff --git a/spec/ruby/library/net-ftp/default_passive_spec.rb b/spec/ruby/library/net-ftp/default_passive_spec.rb new file mode 100644 index 0000000000..3f14f6187e --- /dev/null +++ b/spec/ruby/library/net-ftp/default_passive_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#default_passive" do + it "is true by default" do + ruby_exe(fixture(__FILE__, "default_passive.rb")).should == "true\ntrue\n" + end +end diff --git a/spec/ruby/library/net-ftp/delete_spec.rb b/spec/ruby/library/net-ftp/delete_spec.rb new file mode 100644 index 0000000000..bfb7da1ffe --- /dev/null +++ b/spec/ruby/library/net-ftp/delete_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#delete" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the DELE command with the passed filename to the server" do + @ftp.delete("test.file") + @ftp.last_response.should == "250 Requested file action okay, completed. (DELE test.file)\n" + end + + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:dele).and_respond("450 Requested file action not taken.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:dele).and_respond("550 Requested action not taken.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:dele).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:dele).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:dele).and_respond("502 Command not implemented.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:dele).and_respond("421 Service not available, closing control connection.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:dele).and_respond("530 Not logged in.") + -> { @ftp.delete("test.file") }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/dir_spec.rb b/spec/ruby/library/net-ftp/dir_spec.rb new file mode 100644 index 0000000000..894f03dd7b --- /dev/null +++ b/spec/ruby/library/net-ftp/dir_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/list' + +describe "Net::FTP#dir" do + it_behaves_like :net_ftp_list, :dir +end diff --git a/spec/ruby/library/net-ftp/fixtures/default_passive.rb b/spec/ruby/library/net-ftp/fixtures/default_passive.rb new file mode 100644 index 0000000000..b6995d6f34 --- /dev/null +++ b/spec/ruby/library/net-ftp/fixtures/default_passive.rb @@ -0,0 +1,3 @@ +require "net/ftp" +puts Net::FTP.default_passive +puts Net::FTP.new.passive diff --git a/spec/ruby/library/net-ftp/fixtures/passive.rb b/spec/ruby/library/net-ftp/fixtures/passive.rb new file mode 100644 index 0000000000..6b5cde82df --- /dev/null +++ b/spec/ruby/library/net-ftp/fixtures/passive.rb @@ -0,0 +1,2 @@ +require "net/ftp" +print Net::FTP.new.passive diff --git a/spec/ruby/library/net-ftp/fixtures/putbinaryfile b/spec/ruby/library/net-ftp/fixtures/putbinaryfile new file mode 100644 index 0000000000..f3130c6e43 --- /dev/null +++ b/spec/ruby/library/net-ftp/fixtures/putbinaryfile @@ -0,0 +1,3 @@ +This is an example file +which is going to be transmitted +using #putbinaryfile. diff --git a/spec/ruby/library/net-ftp/fixtures/puttextfile b/spec/ruby/library/net-ftp/fixtures/puttextfile new file mode 100644 index 0000000000..b4f3b2b62d --- /dev/null +++ b/spec/ruby/library/net-ftp/fixtures/puttextfile @@ -0,0 +1,3 @@ +This is an example file +which is going to be transmitted +using #puttextfile. diff --git a/spec/ruby/library/net-ftp/fixtures/server.rb b/spec/ruby/library/net-ftp/fixtures/server.rb new file mode 100644 index 0000000000..8b34d3f8bd --- /dev/null +++ b/spec/ruby/library/net-ftp/fixtures/server.rb @@ -0,0 +1,277 @@ +module NetFTPSpecs + class DummyFTP + attr_accessor :connect_message + attr_reader :login_user, :login_pass, :login_acct + + # hostname or IP address + attr_reader :hostname + # port number + attr_reader :server_port + + def initialize + @hostname = "127.0.0.1" + @server = TCPServer.new(@hostname, 0) + @server_port = @server.addr[1] + + @handlers = {} + @commands = [] + @connect_message = nil + end + + def serve_once + @thread = Thread.new do + @socket = @server.accept + @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1) + begin + handle_request + ensure + @socket.close + end + end + end + + def handle_request + # Send out the welcome message. + response @connect_message || "220 Dummy FTP Server ready!" + + begin + while command = @socket.gets + command, argument = command.chomp.split(" ", 2) + + if command == "QUIT" + self.response("221 OK, bye") + break + elsif proc_handler = @handlers[command.downcase.to_sym] + if argument.nil? + proc_handler.call(self) + else + proc_handler.call(self, argument) + end + else + if argument.nil? + self.send(command.downcase.to_sym) + else + self.send(command.downcase.to_sym, argument) + end + end + end + rescue => e + self.error_response("Exception: #{e} #{e.backtrace.inspect}") + end + end + + def error_response(text) + self.response("451 #{text}") + end + + def response(text) + @socket.puts(text) unless @socket.closed? + end + + def stop + @datasocket.close unless @datasocket.nil? || @datasocket.closed? + @server.close + @thread.join + end + + + ## + def handle(sym, &block) + @handlers[sym] = block + end + + def should_receive(method) + @handler_for = method + self + end + + def and_respond(text) + @handlers[@handler_for] = -> s, *args { s.response(text) } + end + + ## + # FTP methods + ## + + def abor + self.response("226 Closing data connection. (ABOR)") + end + + def acct(account) + @login_acct = account + self.response("230 User '#{account}' logged in, proceed. (ACCT)") + end + + def cdup + self.response("200 Command okay. (CDUP)") + end + + def cwd(dir) + self.response("200 Command okay. (CWD #{dir})") + end + + def dele(file) + self.response("250 Requested file action okay, completed. (DELE #{file})") + end + + def eprt(arg) + _, _, host, port = arg.split("|") + + @datasocket = TCPSocket.new(host, port) + self.response("200 port opened") + end + + def help(param = :default) + if param == :default + self.response("211 System status, or system help reply. (HELP)") + else + self.response("211 System status, or system help reply. (HELP #{param})") + end + end + + def list(folder) + self.response("150 opening ASCII connection for file list") + @datasocket.puts("-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb") + @datasocket.puts("-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb") + @datasocket.puts("-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb") + @datasocket.close() + self.response("226 transfer complete (LIST #{folder})") + end + + def mdtm(filename) + self.response("213 19980705132316") + end + + def mkd(foldername) + self.response(%Q{257 "#{foldername.gsub('"', '""')}" created.}) + end + + def nlst(folder = nil) + self.response("150 opening ASCII connection for file list") + @datasocket.puts("last_response_code.rb") + @datasocket.puts("list.rb") + @datasocket.puts("pwd.rb") + @datasocket.close() + self.response("226 transfer complete (NLST#{folder ? " #{folder}" : ""})") + end + + def noop + self.response("200 Command okay. (NOOP)") + end + + def pass(password) + @login_pass = password + self.response("230 User logged in, proceed. (PASS #{password})") + end + + def port(arg) + nums = arg.split(",") + + if nums[0] == "::1" + # IPv6 + port = nums[1].to_i * 256 + nums[2].to_i + host = nums[0] + else + # IPv4 + port = nums[4].to_i * 256 + nums[5].to_i + host = nums[0..3].join(".") + end + + @datasocket = TCPSocket.new(host, port) + self.response("200 port opened") + end + + def pwd + self.response('257 "/some/dir/" - current directory') + end + + def retr(file) + self.response("125 Data transfer starting") + if @restart_at && @restart_at == 20 + @datasocket.puts("of the file named '#{file}'.") + @restart_at = nil + else + @datasocket.puts("This is the content") + @datasocket.puts("of the file named '#{file}'.") + end + @datasocket.close() + self.response("226 Closing data connection. (RETR #{file})") + end + + def rest(at_bytes) + @restart_at = at_bytes.to_i + self.response("350 Requested file action pending further information. (REST)") + end + + def rmd(folder) + self.response("250 Requested file action okay, completed. (RMD #{folder})") + end + + def rnfr(from) + @rename_from = from + self.response("350 Requested file action pending further information.") + end + + def rnto(to) + self.response("250 Requested file action okay, completed. (Renamed #{@rename_from} to #{to})") + @rename_from = nil + end + + def site(param) + self.response("200 Command okay. (SITE #{param})") + end + + def size(filename) + if filename == "binary" + self.response("213 24") + else + self.response("213 1024") + end + end + + def stat(param = :default) + if param == :default + self.response("211 System status, or system help reply. (STAT)") + else + self.response("211 System status, or system help reply. (STAT #{param})") + end + end + + def stor(file) + tmp_file = tmp("#{file}file", false) + + self.response("125 Data transfer starting.") + + mode = @restart_at ? "a" : "w" + + File.open(tmp_file, mode + "b") do |f| + loop do + data = @datasocket.recv(1024) + break if !data || data.empty? + f << data + end + end + + @datasocket.close() + self.response("200 OK, Data received. (STOR #{file})") + end + + def appe(file) + @restart_at = true + stor(file) + end + + def syst + self.response("215 FTP Dummy Server (SYST)") + end + + def type(type) + self.response("200 TYPE switched to #{type}") + end + + def user(name) + @login_user = name + self.response("230 User logged in, proceed. (USER #{name})") + end + end +end diff --git a/spec/ruby/library/net-ftp/get_spec.rb b/spec/ruby/library/net-ftp/get_spec.rb new file mode 100644 index 0000000000..1bc1bd744b --- /dev/null +++ b/spec/ruby/library/net-ftp/get_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/gettextfile' +require_relative 'shared/getbinaryfile' + +describe "Net::FTP#get (binary mode)" do + before :each do + @binary_mode = true + end + + it_behaves_like :net_ftp_getbinaryfile, :get +end + +describe "Net::FTP#get (text mode)" do + before :each do + @binary_mode = false + end + + it_behaves_like :net_ftp_gettextfile, :get +end diff --git a/spec/ruby/library/net-ftp/getbinaryfile_spec.rb b/spec/ruby/library/net-ftp/getbinaryfile_spec.rb new file mode 100644 index 0000000000..e9898fccc7 --- /dev/null +++ b/spec/ruby/library/net-ftp/getbinaryfile_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/getbinaryfile' + +describe "Net::FTP#getbinaryfile" do + it_behaves_like :net_ftp_getbinaryfile, :getbinaryfile +end diff --git a/spec/ruby/library/net-ftp/getdir_spec.rb b/spec/ruby/library/net-ftp/getdir_spec.rb new file mode 100644 index 0000000000..756d6a23af --- /dev/null +++ b/spec/ruby/library/net-ftp/getdir_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'shared/pwd' + +describe "Net::FTP#getdir" do + it_behaves_like :net_ftp_pwd, :getdir +end diff --git a/spec/ruby/library/net-ftp/gettextfile_spec.rb b/spec/ruby/library/net-ftp/gettextfile_spec.rb new file mode 100644 index 0000000000..cdd1b4c797 --- /dev/null +++ b/spec/ruby/library/net-ftp/gettextfile_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/gettextfile' + +describe "Net::FTP#gettextfile" do + it_behaves_like :net_ftp_gettextfile, :gettextfile +end diff --git a/spec/ruby/library/net-ftp/help_spec.rb b/spec/ruby/library/net-ftp/help_spec.rb new file mode 100644 index 0000000000..c562be50b2 --- /dev/null +++ b/spec/ruby/library/net-ftp/help_spec.rb @@ -0,0 +1,66 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#help" do + def with_connection + yield + end + + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "writes the HELP command to the server" do + @ftp.help + @ftp.last_response.should == "211 System status, or system help reply. (HELP)\n" + end + + it "returns the server's response" do + @ftp.help.should == "211 System status, or system help reply. (HELP)\n" + end + + it "writes the HELP command with an optional parameter to the socket" do + @ftp.help("some parameter").should == "211 System status, or system help reply. (HELP some parameter)\n" + end + + it "does not raise any error when the response code is 211" do + @server.should_receive(:help).and_respond("211 System status, or system help reply.") + -> { @ftp.help }.should_not raise_error + end + + it "does not raise any error when the response code is 214" do + @server.should_receive(:help).and_respond("214 Help message.") + -> { @ftp.help }.should_not raise_error + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:help).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.help }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:help).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.help }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:help).and_respond("502 Command not implemented.") + -> { @ftp.help }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:help).and_respond("421 Service not available, closing control connection.") + -> { @ftp.help }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/initialize_spec.rb b/spec/ruby/library/net-ftp/initialize_spec.rb new file mode 100644 index 0000000000..4d775e8dc1 --- /dev/null +++ b/spec/ruby/library/net-ftp/initialize_spec.rb @@ -0,0 +1,405 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#initialize" do + before :each do + @ftp = Net::FTP.allocate + @ftp.stub!(:connect) + @port_args = [] + @port_args << 21 + end + + it "is private" do + Net::FTP.should have_private_instance_method(:initialize) + end + + it "sets self into binary mode" do + @ftp.binary.should be_nil + @ftp.send(:initialize) + @ftp.binary.should be_true + end + + it "sets self into active mode" do + @ftp.passive.should be_nil + @ftp.send(:initialize) + @ftp.passive.should be_false + end + + it "sets self into non-debug mode" do + @ftp.debug_mode.should be_nil + @ftp.send(:initialize) + @ftp.debug_mode.should be_false + end + + it "sets self to not resume file uploads/downloads" do + @ftp.resume.should be_nil + @ftp.send(:initialize) + @ftp.resume.should be_false + end + + describe "when passed no arguments" do + it "does not try to connect" do + @ftp.should_not_receive(:connect) + @ftp.send(:initialize) + end + end + + describe "when passed host" do + it "tries to connect to the passed host" do + @ftp.should_receive(:connect).with("localhost", *@port_args) + @ftp.send(:initialize, "localhost") + end + end + + describe "when passed host, user" do + it "tries to connect to the passed host" do + @ftp.should_receive(:connect).with("localhost", *@port_args) + @ftp.send(:initialize, "localhost") + end + + it "tries to login with the passed username" do + @ftp.should_receive(:login).with("rubyspec", nil, nil) + @ftp.send(:initialize, "localhost", "rubyspec") + end + end + + describe "when passed host, user, password" do + it "tries to connect to the passed host" do + @ftp.should_receive(:connect).with("localhost", *@port_args) + @ftp.send(:initialize, "localhost") + end + + it "tries to login with the passed username and password" do + @ftp.should_receive(:login).with("rubyspec", "rocks", nil) + @ftp.send(:initialize, "localhost", "rubyspec", "rocks") + end + end + + describe "when passed host, user" do + it "tries to connect to the passed host" do + @ftp.should_receive(:connect).with("localhost", *@port_args) + @ftp.send(:initialize, "localhost") + end + + it "tries to login with the passed username, password and account" do + @ftp.should_receive(:login).with("rubyspec", "rocks", "account") + @ftp.send(:initialize, "localhost", "rubyspec", "rocks", "account") + end + end + + before :each do + @ftp.stub!(:login) + end + + describe 'when the host' do + describe 'is set' do + describe 'and port option' do + describe 'is set' do + it 'tries to connect to the host on the specified port' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ port: 8080 }) + @ftp.should_receive(:connect).with('localhost', 8080) + + @ftp.send(:initialize, 'localhost', options) + end + end + + describe 'is not set' do + it 'tries to connect to the host without a port' do + @ftp.should_receive(:connect).with("localhost", *@port_args) + + @ftp.send(:initialize, 'localhost') + end + end + end + + describe 'when the username option' do + describe 'is set' do + describe 'and the password option' do + describe 'is set' do + describe 'and the account option' do + describe 'is set' do + it 'tries to log in with the supplied parameters' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ username: 'a', password: 'topsecret', account: 'b' }) + @ftp.should_receive(:login).with('a', 'topsecret', 'b') + + @ftp.send(:initialize, 'localhost', options) + end + end + + describe 'is unset' do + it 'tries to log in with the supplied parameters' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ username: 'a', password: 'topsecret' }) + @ftp.should_receive(:login).with('a', 'topsecret', nil) + + @ftp.send(:initialize, 'localhost', options) + end + end + end + end + + describe 'is unset' do + describe 'and the account option' do + describe 'is set' do + it 'tries to log in with the supplied parameters' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ username: 'a', account: 'b' }) + @ftp.should_receive(:login).with('a', nil, 'b') + + @ftp.send(:initialize, 'localhost', options) + end + end + + describe 'is unset' do + it 'tries to log in with the supplied parameters' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ username: 'a'}) + @ftp.should_receive(:login).with('a', nil, nil) + + @ftp.send(:initialize, 'localhost', options) + end + end + end + end + end + end + + describe 'is not set' do + it 'does not try to log in' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({}) + @ftp.should_not_receive(:login) + + @ftp.send(:initialize, 'localhost', options) + end + end + end + end + + describe 'is unset' do + it 'does not try to connect' do + @ftp.should_not_receive(:connect) + + @ftp.send(:initialize) + end + + it 'does not try to log in' do + @ftp.should_not_receive(:login) + + @ftp.send(:initialize) + end + end + end + + describe 'when the passive option' do + describe 'is set' do + describe 'to true' do + it 'sets passive to true' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ passive: true }) + + @ftp.send(:initialize, nil, options) + @ftp.passive.should == true + end + end + + describe 'to false' do + it 'sets passive to false' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ passive: false }) + + @ftp.send(:initialize, nil, options) + @ftp.passive.should == false + end + end + end + + describe 'is unset' do + it 'sets passive to false' do + @ftp.send(:initialize) + @ftp.passive.should == false + end + end + end + + describe 'when the debug_mode option' do + describe 'is set' do + describe 'to true' do + it 'sets debug_mode to true' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ debug_mode: true }) + + @ftp.send(:initialize, nil, options) + @ftp.debug_mode.should == true + end + end + + describe 'to false' do + it 'sets debug_mode to false' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ debug_mode: false }) + + @ftp.send(:initialize, nil, options) + @ftp.debug_mode.should == false + end + end + end + + describe 'is unset' do + it 'sets debug_mode to false' do + @ftp.send(:initialize) + @ftp.debug_mode.should == false + end + end + end + + describe 'when the open_timeout option' do + describe 'is set' do + it 'sets open_timeout to the specified value' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ open_timeout: 42 }) + + @ftp.send(:initialize, nil, options) + @ftp.open_timeout.should == 42 + end + end + + describe 'is not set' do + it 'sets open_timeout to nil' do + @ftp.send(:initialize) + @ftp.open_timeout.should == nil + end + end + end + + describe 'when the read_timeout option' do + describe 'is set' do + it 'sets read_timeout to the specified value' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ read_timeout: 100 }) + + @ftp.send(:initialize, nil, options) + @ftp.read_timeout.should == 100 + end + end + + describe 'is not set' do + it 'sets read_timeout to the default value' do + @ftp.send(:initialize) + @ftp.read_timeout.should == 60 + end + end + end + + describe 'when the ssl_handshake_timeout option' do + describe 'is set' do + it 'sets ssl_handshake_timeout to the specified value' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ ssl_handshake_timeout: 23 }) + + @ftp.send(:initialize, nil, options) + @ftp.ssl_handshake_timeout.should == 23 + end + end + + describe 'is not set' do + it 'sets ssl_handshake_timeout to nil' do + @ftp.send(:initialize) + @ftp.ssl_handshake_timeout.should == nil + end + end + end + + describe 'when the ssl option' do + describe 'is set' do + describe "and the ssl option's value is true" do + it 'initializes ssl_context to a blank SSLContext object' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ ssl: true }) + + ssl_context = OpenSSL::SSL::SSLContext.allocate + ssl_context.stub!(:set_params) + + OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ssl_context) + ssl_context.should_receive(:set_params).with({}) + + @ftp.send(:initialize, nil, options) + @ftp.instance_variable_get(:@ssl_context).should == ssl_context + end + end + + describe "and the ssl option's value is a hash" do + it 'initializes ssl_context to a configured SSLContext object' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ ssl: {key: 'value'} }) + + ssl_context = OpenSSL::SSL::SSLContext.allocate + ssl_context.stub!(:set_params) + + OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ssl_context) + ssl_context.should_receive(:set_params).with({key: 'value'}) + + @ftp.send(:initialize, nil, options) + @ftp.instance_variable_get(:@ssl_context).should == ssl_context + end + end + + describe 'and private_data_connection' do + describe 'is set' do + it 'sets private_data_connection to that value' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ ssl: true, private_data_connection: 'true' }) + + @ftp.send(:initialize, nil, options) + @ftp.instance_variable_get(:@private_data_connection).should == 'true' + end + end + + describe 'is not set' do + it 'sets private_data_connection to nil' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ ssl: true }) + + @ftp.send(:initialize, nil, options) + @ftp.instance_variable_get(:@private_data_connection).should == true + end + end + end + end + + describe 'is not set' do + it 'sets ssl_context to nil' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({}) + + @ftp.send(:initialize, nil, options) + @ftp.instance_variable_get(:@ssl_context).should == nil + end + + describe 'private_data_connection' do + describe 'is set' do + it 'raises an ArgumentError' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({ private_data_connection: true }) + + -> { + @ftp.send(:initialize, nil, options) + }.should raise_error(ArgumentError, /private_data_connection can be set to true only when ssl is enabled/) + end + end + + describe 'is not set' do + it 'sets private_data_connection to false' do + options = mock('ftp initialize options') + options.should_receive(:to_hash).and_return({}) + + @ftp.send(:initialize, nil, options) + @ftp.instance_variable_get(:@private_data_connection).should == false + end + end + end + end + end +end diff --git a/spec/ruby/library/net-ftp/last_response_code_spec.rb b/spec/ruby/library/net-ftp/last_response_code_spec.rb new file mode 100644 index 0000000000..c17c28f0f8 --- /dev/null +++ b/spec/ruby/library/net-ftp/last_response_code_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'shared/last_response_code' +require_relative 'fixtures/server' + +describe "Net::FTP#last_response_code" do + it_behaves_like :net_ftp_last_response_code, :last_response_code +end diff --git a/spec/ruby/library/net-ftp/last_response_spec.rb b/spec/ruby/library/net-ftp/last_response_spec.rb new file mode 100644 index 0000000000..c9d9d70f35 --- /dev/null +++ b/spec/ruby/library/net-ftp/last_response_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#last_response" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "returns the last response" do + @ftp.last_response.should == "220 Dummy FTP Server ready!\n" + @ftp.help + @ftp.last_response.should == "211 System status, or system help reply. (HELP)\n" + end +end diff --git a/spec/ruby/library/net-ftp/lastresp_spec.rb b/spec/ruby/library/net-ftp/lastresp_spec.rb new file mode 100644 index 0000000000..e0c1b862a0 --- /dev/null +++ b/spec/ruby/library/net-ftp/lastresp_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'shared/last_response_code' +require_relative 'fixtures/server' + +describe "Net::FTP#lastresp" do + it_behaves_like :net_ftp_last_response_code, :lastresp +end diff --git a/spec/ruby/library/net-ftp/list_spec.rb b/spec/ruby/library/net-ftp/list_spec.rb new file mode 100644 index 0000000000..6cb1bbc4b8 --- /dev/null +++ b/spec/ruby/library/net-ftp/list_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/list' + +describe "Net::FTP#list" do + it_behaves_like :net_ftp_list, :list +end diff --git a/spec/ruby/library/net-ftp/login_spec.rb b/spec/ruby/library/net-ftp/login_spec.rb new file mode 100644 index 0000000000..0de2f5cc63 --- /dev/null +++ b/spec/ruby/library/net-ftp/login_spec.rb @@ -0,0 +1,195 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#login" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + describe "when passed no arguments" do + it "sends the USER command with 'anonymous' as name to the server" do + @ftp.login + @server.login_user.should == "anonymous" + end + + it "sends 'anonymous@' as a password when required" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @ftp.login + @server.login_pass.should == "anonymous@" + end + + it "raises a Net::FTPReplyError when the server requests an account" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @server.should_receive(:pass).and_respond("332 Need account for login.") + -> { @ftp.login }.should raise_error(Net::FTPReplyError) + end + end + + describe "when passed name" do + it "sends the USER command with the passed name to the server" do + @ftp.login("rubyspec") + @server.login_user.should == "rubyspec" + end + + it "raises a Net::FTPReplyError when the server requests a password, but none was given" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + -> { @ftp.login("rubyspec") }.should raise_error(Net::FTPReplyError) + end + + it "raises a Net::FTPReplyError when the server requests an account, but none was given" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @server.should_receive(:pass).and_respond("332 Need account for login.") + -> { @ftp.login("rubyspec") }.should raise_error(Net::FTPReplyError) + end + end + + describe "when passed name, password" do + it "sends the USER command with the passed name to the server" do + @ftp.login("rubyspec", "rocks") + @server.login_user.should == "rubyspec" + end + + it "sends the passed password when required" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @ftp.login("rubyspec", "rocks") + @server.login_pass.should == "rocks" + end + + it "raises a Net::FTPReplyError when the server requests an account" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @server.should_receive(:pass).and_respond("332 Need account for login.") + -> { @ftp.login("rubyspec", "rocks") }.should raise_error(Net::FTPReplyError) + end + end + + describe "when passed name, password, account" do + it "sends the USER command with the passed name to the server" do + @ftp.login("rubyspec", "rocks", "account") + @server.login_user.should == "rubyspec" + end + + it "sends the passed password when required" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @ftp.login("rubyspec", "rocks", "account") + @server.login_pass.should == "rocks" + end + + it "sends the passed account when required" do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @server.should_receive(:pass).and_respond("332 Need account for login.") + @ftp.login("rubyspec", "rocks", "account") + @server.login_acct.should == "account" + end + end + + describe "when the USER command fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:user).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:user).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:user).and_respond("502 Command not implemented.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:user).and_respond("421 Service not available, closing control connection.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:user).and_respond("530 Not logged in.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + end + + describe "when the PASS command fails" do + before :each do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + end + + it "does not raise an Error when the response code is 202" do + @server.should_receive(:pass).and_respond("202 Command not implemented, superfluous at this site.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should_not raise_error + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:pass).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:pass).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:pass).and_respond("502 Command not implemented.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:pass).and_respond("421 Service not available, closing control connection.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:pass).and_respond("530 Not logged in.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + end + + describe "when the ACCT command fails" do + before :each do + @server.should_receive(:user).and_respond("331 User name okay, need password.") + @server.should_receive(:pass).and_respond("332 Need account for login.") + end + + it "does not raise an Error when the response code is 202" do + @server.should_receive(:acct).and_respond("202 Command not implemented, superfluous at this site.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should_not raise_error + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:acct).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:acct).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:acct).and_respond("502 Command not implemented.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:acct).and_respond("421 Service not available, closing control connection.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:acct).and_respond("530 Not logged in.") + -> { @ftp.login("rubyspec", "rocks", "account") }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/ls_spec.rb b/spec/ruby/library/net-ftp/ls_spec.rb new file mode 100644 index 0000000000..acd7e9e523 --- /dev/null +++ b/spec/ruby/library/net-ftp/ls_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/list' + +describe "Net::FTP#ls" do + it_behaves_like :net_ftp_list, :ls +end diff --git a/spec/ruby/library/net-ftp/mdtm_spec.rb b/spec/ruby/library/net-ftp/mdtm_spec.rb new file mode 100644 index 0000000000..a504507c84 --- /dev/null +++ b/spec/ruby/library/net-ftp/mdtm_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#mdtm" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the MDTM with the passed filename command to the server" do + @ftp.mdtm("test.file") + @ftp.last_response.should == "213 19980705132316\n" + end + + it "returns the last modification time of the passed file" do + @ftp.mdtm("test.file").should == "19980705132316" + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:mdtm).and_respond("550 Requested action not taken.") + -> { @ftp.mdtm("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:mdtm).and_respond("421 Service not available, closing control connection.") + -> { @ftp.mdtm("test.file") }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/mkdir_spec.rb b/spec/ruby/library/net-ftp/mkdir_spec.rb new file mode 100644 index 0000000000..8cc6ae785e --- /dev/null +++ b/spec/ruby/library/net-ftp/mkdir_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#mkdir" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the MKD command with the passed pathname to the server" do + @ftp.mkdir("test.folder") + @ftp.last_response.should == %{257 "test.folder" created.\n} + end + + it "returns the path to the newly created directory" do + @ftp.mkdir("test.folder").should == "test.folder" + @ftp.mkdir("/absolute/path/to/test.folder").should == "/absolute/path/to/test.folder" + @ftp.mkdir("relative/path/to/test.folder").should == "relative/path/to/test.folder" + @ftp.mkdir('/usr/dm/foo"bar').should == '/usr/dm/foo"bar' + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:mkd).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:mkd).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:mkd).and_respond("502 Command not implemented.") + -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:mkd).and_respond("421 Service not available, closing control connection.") + -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:mkd).and_respond("530 Not logged in.") + -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:mkd).and_respond("550 Requested action not taken.") + -> { @ftp.mkdir("test.folder") }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/mtime_spec.rb b/spec/ruby/library/net-ftp/mtime_spec.rb new file mode 100644 index 0000000000..9dde1278a8 --- /dev/null +++ b/spec/ruby/library/net-ftp/mtime_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#mtime" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the MDTM with the passed filename command to the server" do + @ftp.mtime("test.file") + @ftp.last_response.should == "213 19980705132316\n" + end + + describe "when passed filename" do + it "returns the last modification time of the passed file as a Time object in the local time" do + @ftp.mtime("test.file").should == Time.gm("1998", "07", "05", "13", "23", "16") + end + end + + describe "when passed filename, local_time" do + it "returns the last modification time as a Time object in UTC when local_time is true" do + @ftp.mtime("test.file", true).should == Time.local("1998", "07", "05", "13", "23", "16") + end + + it "returns the last modification time as a Time object in the local time when local_time is false" do + @ftp.mtime("test.file", false).should == Time.gm("1998", "07", "05", "13", "23", "16") + end + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:mdtm).and_respond("550 Requested action not taken.") + -> { @ftp.mtime("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:mdtm).and_respond("421 Service not available, closing control connection.") + -> { @ftp.mtime("test.file") }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/nlst_spec.rb b/spec/ruby/library/net-ftp/nlst_spec.rb new file mode 100644 index 0000000000..2f22543af6 --- /dev/null +++ b/spec/ruby/library/net-ftp/nlst_spec.rb @@ -0,0 +1,92 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#nlst" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.passive = false + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + describe "when passed no arguments" do + it "returns an Array containing a list of files in the current dir" do + @ftp.nlst.should == ["last_response_code.rb", "list.rb", "pwd.rb"] + @ftp.last_response.should == "226 transfer complete (NLST)\n" + end + end + + describe "when passed dir" do + it "returns an Array containing a list of files in the passed dir" do + @ftp.nlst("test.folder").should == ["last_response_code.rb", "list.rb", "pwd.rb"] + @ftp.last_response.should == "226 transfer complete (NLST test.folder)\n" + end + end + + describe "when the NLST command fails" do + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:nlst).and_respond("450 Requested file action not taken..") + -> { @ftp.nlst }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:nlst).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:nlst).and_respond("501 Syntax error, command unrecognized.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:nlst).and_respond("502 Command not implemented.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:nlst).and_respond("421 Service not available, closing control connection.") + -> { @ftp.nlst }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:nlst).and_respond("530 Not logged in.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + end + + describe "when opening the data port fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.") + @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.") + @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.") + @server.should_receive(:port).and_respond("421 Service not available, closing control connection.") + -> { @ftp.nlst }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:eprt).and_respond("530 Not logged in.") + @server.should_receive(:port).and_respond("530 Not logged in.") + -> { @ftp.nlst }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/noop_spec.rb b/spec/ruby/library/net-ftp/noop_spec.rb new file mode 100644 index 0000000000..4743a39ef6 --- /dev/null +++ b/spec/ruby/library/net-ftp/noop_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#noop" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the NOOP command to the server" do + @ftp.noop + @ftp.last_response.should == "200 Command okay. (NOOP)\n" + end + + it "returns nil" do + @ftp.noop.should be_nil + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:noop).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.noop }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:noop).and_respond("421 Service not available, closing control connection.") + -> { @ftp.noop }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/open_spec.rb b/spec/ruby/library/net-ftp/open_spec.rb new file mode 100644 index 0000000000..e59496dc3c --- /dev/null +++ b/spec/ruby/library/net-ftp/open_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP.open" do + before :each do + @ftp = mock("Net::FTP instance") + Net::FTP.stub!(:new).and_return(@ftp) + end + + describe "when passed no block" do + it "returns a new Net::FTP instance" do + Net::FTP.open("localhost").should equal(@ftp) + end + + it "passes the passed arguments down to Net::FTP.new" do + Net::FTP.should_receive(:new).with("localhost", "user", "password", "account") + Net::FTP.open("localhost", "user", "password", "account") + end + end + + describe "when passed a block" do + before :each do + @ftp.stub!(:close) + end + + it "yields a new Net::FTP instance to the passed block" do + yielded = false + Net::FTP.open("localhost") do |ftp| + yielded = true + ftp.should equal(@ftp) + end + yielded.should be_true + end + + it "closes the Net::FTP instance after yielding" do + Net::FTP.open("localhost") do |ftp| + ftp.should_receive(:close) + end + end + + it "closes the Net::FTP instance even if an exception is raised while yielding" do + begin + Net::FTP.open("localhost") do |ftp| + ftp.should_receive(:close) + raise ArgumentError, "some exception" + end + rescue ArgumentError + end + end + + it "returns the block's return value" do + Net::FTP.open("localhost") { :test }.should == :test + end + end +end diff --git a/spec/ruby/library/net-ftp/passive_spec.rb b/spec/ruby/library/net-ftp/passive_spec.rb new file mode 100644 index 0000000000..97659f1b68 --- /dev/null +++ b/spec/ruby/library/net-ftp/passive_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#passive" do + it "returns true when self is in passive mode" do + ftp = Net::FTP.new + ftp.passive.should be_false + + ftp.passive = true + ftp.passive.should be_true + end + + it "is the value of Net::FTP.default_value by default" do + ruby_exe(fixture(__FILE__, "passive.rb")).should == "true" + end +end + +describe "Net::FTP#passive=" do + it "sets self to passive mode when passed true" do + ftp = Net::FTP.new + + ftp.passive = true + ftp.passive.should be_true + + ftp.passive = false + ftp.passive.should be_false + end +end diff --git a/spec/ruby/library/net-ftp/put_spec.rb b/spec/ruby/library/net-ftp/put_spec.rb new file mode 100644 index 0000000000..6d40d3d5b9 --- /dev/null +++ b/spec/ruby/library/net-ftp/put_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/puttextfile' +require_relative 'shared/putbinaryfile' + +describe "Net::FTP#put (binary mode)" do + before :each do + @binary_mode = true + end + + it_behaves_like :net_ftp_putbinaryfile, :put +end + +describe "Net::FTP#put (text mode)" do + before :each do + @binary_mode = false + end + + it_behaves_like :net_ftp_puttextfile, :put +end diff --git a/spec/ruby/library/net-ftp/putbinaryfile_spec.rb b/spec/ruby/library/net-ftp/putbinaryfile_spec.rb new file mode 100644 index 0000000000..d0398229e5 --- /dev/null +++ b/spec/ruby/library/net-ftp/putbinaryfile_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/putbinaryfile' + +describe "Net::FTP#putbinaryfile" do + it_behaves_like :net_ftp_putbinaryfile, :putbinaryfile +end diff --git a/spec/ruby/library/net-ftp/puttextfile_spec.rb b/spec/ruby/library/net-ftp/puttextfile_spec.rb new file mode 100644 index 0000000000..b8bcac33df --- /dev/null +++ b/spec/ruby/library/net-ftp/puttextfile_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' +require_relative 'shared/puttextfile' + +describe "Net::FTP#puttextfile" do + it_behaves_like :net_ftp_puttextfile, :puttextfile +end diff --git a/spec/ruby/library/net-ftp/pwd_spec.rb b/spec/ruby/library/net-ftp/pwd_spec.rb new file mode 100644 index 0000000000..992e2c4ed2 --- /dev/null +++ b/spec/ruby/library/net-ftp/pwd_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#pwd" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the PWD command to the server" do + @ftp.pwd + @ftp.last_response.should == "257 \"/some/dir/\" - current directory\n" + end + + it "returns the current directory" do + @ftp.pwd.should == "/some/dir/" + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:pwd).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.pwd }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:pwd).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.pwd }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:pwd).and_respond("502 Command not implemented.") + -> { @ftp.pwd }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:pwd).and_respond("421 Service not available, closing control connection.") + -> { @ftp.pwd }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:pwd).and_respond("550 Requested action not taken.") + -> { @ftp.pwd }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/quit_spec.rb b/spec/ruby/library/net-ftp/quit_spec.rb new file mode 100644 index 0000000000..c5352ceada --- /dev/null +++ b/spec/ruby/library/net-ftp/quit_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#quit" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the QUIT command to the server" do + @ftp.quit + @ftp.last_response.should == "221 OK, bye\n" + end + + it "does not close the socket automatically" do + @ftp.quit + @ftp.closed?.should be_false + end + + it "returns nil" do + @ftp.quit.should be_nil + end +end diff --git a/spec/ruby/library/net-ftp/rename_spec.rb b/spec/ruby/library/net-ftp/rename_spec.rb new file mode 100644 index 0000000000..48f81b7deb --- /dev/null +++ b/spec/ruby/library/net-ftp/rename_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#rename" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + describe "when passed from_name, to_name" do + it "sends the RNFR command with the passed from_name and the RNTO command with the passed to_name to the server" do + @ftp.rename("from.file", "to.file") + @ftp.last_response.should == "250 Requested file action okay, completed. (Renamed from.file to to.file)\n" + end + + it "returns something" do + @ftp.rename("from.file", "to.file").should be_nil + end + end + + describe "when the RNFR command fails" do + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:rnfr).and_respond("450 Requested file action not taken.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:rnfr).and_respond("550 Requested action not taken.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:rnfr).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:rnfr).and_respond("502 Command not implemented.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:rnfr).and_respond("421 Service not available, closing control connection.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:rnfr).and_respond("530 Not logged in.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + end + + describe "when the RNTO command fails" do + it "raises a Net::FTPPermError when the response code is 532" do + @server.should_receive(:rnfr).and_respond("532 Need account for storing files.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 553" do + @server.should_receive(:rnto).and_respond("553 Requested action not taken.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:rnto).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:rnto).and_respond("502 Command not implemented.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:rnto).and_respond("421 Service not available, closing control connection.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:rnto).and_respond("530 Not logged in.") + -> { @ftp.rename("from.file", "to.file") }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/resume_spec.rb b/spec/ruby/library/net-ftp/resume_spec.rb new file mode 100644 index 0000000000..6592fc5bb0 --- /dev/null +++ b/spec/ruby/library/net-ftp/resume_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#resume" do + it "returns true when self is set to resume uploads/downloads" do + ftp = Net::FTP.new + ftp.resume.should be_false + + ftp.resume = true + ftp.resume.should be_true + end +end + +describe "Net::FTP#resume=" do + it "sets self to resume uploads/downloads when set to true" do + ftp = Net::FTP.new + ftp.resume = true + ftp.resume.should be_true + + ftp.resume = false + ftp.resume.should be_false + end +end diff --git a/spec/ruby/library/net-ftp/retrbinary_spec.rb b/spec/ruby/library/net-ftp/retrbinary_spec.rb new file mode 100644 index 0000000000..de024208aa --- /dev/null +++ b/spec/ruby/library/net-ftp/retrbinary_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#retrbinary" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the passed command to the server" do + @ftp.retrbinary("RETR test", 4096) {} + @ftp.last_response.should == "226 Closing data connection. (RETR test)\n" + end + + it "yields the received content as binary blocks of the passed size" do + res = [] + @ftp.retrbinary("RETR test", 10) { |bin| res << bin } + res.should == [ "This is th", "e content\n", "of the fil", "e named 't", "est'.\n" ] + end +end diff --git a/spec/ruby/library/net-ftp/retrlines_spec.rb b/spec/ruby/library/net-ftp/retrlines_spec.rb new file mode 100644 index 0000000000..866ecb5f40 --- /dev/null +++ b/spec/ruby/library/net-ftp/retrlines_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#retrlines" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the passed command over the socket" do + @ftp.retrlines("LIST test.dir") {} + @ftp.last_response.should == "226 transfer complete (LIST test.dir)\n" + end + + it "yields each received line to the passed block" do + res = [] + @ftp.retrlines("LIST test.dir") { |x| res << x } + res.should == [ + "-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb", + "-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb", + "-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb" + ] + end +end diff --git a/spec/ruby/library/net-ftp/return_code_spec.rb b/spec/ruby/library/net-ftp/return_code_spec.rb new file mode 100644 index 0000000000..35a6232f7e --- /dev/null +++ b/spec/ruby/library/net-ftp/return_code_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#return_code" do + before :each do + @ftp = Net::FTP.new + end + + it "outputs a warning and returns a newline" do + -> do + @ftp.return_code.should == "\n" + end.should complain(/warning: Net::FTP#return_code is obsolete and do nothing/) + end +end + +describe "Net::FTP#return_code=" do + before :each do + @ftp = Net::FTP.new + end + + it "outputs a warning" do + -> { @ftp.return_code = 123 }.should complain(/warning: Net::FTP#return_code= is obsolete and do nothing/) + end +end diff --git a/spec/ruby/library/net-ftp/rmdir_spec.rb b/spec/ruby/library/net-ftp/rmdir_spec.rb new file mode 100644 index 0000000000..400874d60d --- /dev/null +++ b/spec/ruby/library/net-ftp/rmdir_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#rmdir" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the RMD command with the passed pathname to the server" do + @ftp.rmdir("test.folder") + @ftp.last_response.should == "250 Requested file action okay, completed. (RMD test.folder)\n" + end + + it "returns nil" do + @ftp.rmdir("test.folder").should be_nil + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:rmd).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:rmd).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:rmd).and_respond("502 Command not implemented.") + -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:rmd).and_respond("421 Service not available, closing control connection.") + -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:rmd).and_respond("530 Not logged in.") + -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:rmd).and_respond("550 Requested action not taken.") + -> { @ftp.rmdir("test.folder") }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/sendcmd_spec.rb b/spec/ruby/library/net-ftp/sendcmd_spec.rb new file mode 100644 index 0000000000..c50b373869 --- /dev/null +++ b/spec/ruby/library/net-ftp/sendcmd_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#sendcmd" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the passed command to the server" do + @ftp.sendcmd("HELP") + @ftp.last_response.should == "211 System status, or system help reply. (HELP)\n" + end + + it "returns the server's response" do + @ftp.sendcmd("HELP").should == "211 System status, or system help reply. (HELP)\n" + end + + it "raises no error when the response code is 1xx, 2xx or 3xx" do + @server.should_receive(:help).and_respond("120 Service ready in nnn minutes.") + -> { @ftp.sendcmd("HELP") }.should_not raise_error + + @server.should_receive(:help).and_respond("200 Command okay.") + -> { @ftp.sendcmd("HELP") }.should_not raise_error + + @server.should_receive(:help).and_respond("350 Requested file action pending further information.") + -> { @ftp.sendcmd("HELP") }.should_not raise_error + end + + it "raises a Net::FTPTempError when the response code is 4xx" do + @server.should_receive(:help).and_respond("421 Service not available, closing control connection.") + -> { @ftp.sendcmd("HELP") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 5xx" do + @server.should_receive(:help).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.sendcmd("HELP") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPProtoError when the response code is not between 1xx-5xx" do + @server.should_receive(:help).and_respond("999 Invalid response.") + -> { @ftp.sendcmd("HELP") }.should raise_error(Net::FTPProtoError) + end +end diff --git a/spec/ruby/library/net-ftp/set_socket_spec.rb b/spec/ruby/library/net-ftp/set_socket_spec.rb new file mode 100644 index 0000000000..8182dd8b33 --- /dev/null +++ b/spec/ruby/library/net-ftp/set_socket_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' + +describe "Net::FTP#set_socket" do + # TODO: I won't spec this method, as it is not used + # anywhere and it should be private anyway. + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/net-ftp/shared/getbinaryfile.rb b/spec/ruby/library/net-ftp/shared/getbinaryfile.rb new file mode 100644 index 0000000000..ceec8e7cd5 --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/getbinaryfile.rb @@ -0,0 +1,150 @@ +describe :net_ftp_getbinaryfile, shared: true do + before :each do + @fixture_file = __dir__ + "/../fixtures/getbinaryfile" + @tmp_file = tmp("getbinaryfile") + + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + @ftp.binary = @binary_mode + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + + rm_r @tmp_file + end + + it "sends the RETR command to the server" do + @ftp.send(@method, "test", @tmp_file) + @ftp.last_response.should == "226 Closing data connection. (RETR test)\n" + end + + it "returns nil" do + @ftp.send(@method, "test", @tmp_file).should be_nil + end + + it "saves the contents of the passed remote file to the passed local file" do + @ftp.send(@method, "test", @tmp_file) + File.read(@tmp_file).should == "This is the content\nof the file named 'test'.\n" + end + + describe "when passed a block" do + it "yields the received content as binary blocks of the passed size" do + res = [] + @ftp.send(@method, "test", @tmp_file, 10) { |bin| res << bin } + res.should == [ "This is th", "e content\n", "of the fil", "e named 't", "est'.\n" ] + end + end + + describe "when resuming an existing file" do + before :each do + @tmp_file = tmp("getbinaryfile_resume") + + File.open(@tmp_file, "wb") do |f| + f << "This is the content\n" + end + + @ftp.resume = true + end + + it "saves the remaining content of the passed remote file to the passed local file" do + @ftp.send(@method, "test", @tmp_file) + File.read(@tmp_file).should == "This is the content\nof the file named 'test'.\n" + end + + describe "and the REST command fails" do + it "raises a Net::FTPProtoError when the response code is 550" do + @server.should_receive(:rest).and_respond("Requested action not taken.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:rest).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:rest).and_respond("501 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:rest).and_respond("502 Command not implemented.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:rest).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:rest).and_respond("530 Not logged in.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + end + end + + describe "when the RETR command fails" do + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:retr).and_respond("450 Requested file action not taken.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPProtoError when the response code is 550" do + @server.should_receive(:retr).and_respond("Requested action not taken.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:retr).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:retr).and_respond("501 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:retr).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:retr).and_respond("530 Not logged in.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + end + + describe "when opening the data port fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.") + @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.") + @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.") + @server.should_receive(:port).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:eprt).and_respond("530 Not logged in.") + @server.should_receive(:port).and_respond("530 Not logged in.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/shared/gettextfile.rb b/spec/ruby/library/net-ftp/shared/gettextfile.rb new file mode 100644 index 0000000000..7fe14f7dfb --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/gettextfile.rb @@ -0,0 +1,100 @@ +describe :net_ftp_gettextfile, shared: true do + before :each do + @tmp_file = tmp("gettextfile") + + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + @ftp.binary = @binary_mode + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + + rm_r @tmp_file + end + + it "sends the RETR command to the server" do + @ftp.send(@method, "test", @tmp_file) + @ftp.last_response.should == "226 Closing data connection. (RETR test)\n" + end + + it "returns nil" do + @ftp.send(@method, "test", @tmp_file).should be_nil + end + + it "saves the contents of the passed remote file to the passed local file" do + @ftp.send(@method, "test", @tmp_file) + File.read(@tmp_file).should == "This is the content\nof the file named 'test'.\n" + end + + describe "when passed a block" do + it "yields each line of the retrieved file to the passed block" do + res = [] + @ftp.send(@method, "test", @tmp_file) { |line| res << line } + res.should == [ "This is the content", "of the file named 'test'."] + end + end + + describe "when the RETR command fails" do + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:retr).and_respond("450 Requested file action not taken.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPProtoError when the response code is 550" do + @server.should_receive(:retr).and_respond("Requested action not taken.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:retr).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:retr).and_respond("501 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:retr).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:retr).and_respond("530 Not logged in.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + end + + describe "when opening the data port fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.") + @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.") + @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.") + @server.should_receive(:port).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:eprt).and_respond("530 Not logged in.") + @server.should_receive(:port).and_respond("530 Not logged in.") + -> { @ftp.send(@method, "test", @tmp_file) }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/shared/last_response_code.rb b/spec/ruby/library/net-ftp/shared/last_response_code.rb new file mode 100644 index 0000000000..4fe53677db --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/last_response_code.rb @@ -0,0 +1,25 @@ +describe :net_ftp_last_response_code, shared: true do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "returns the response code for the last response" do + @server.should_receive(:help).and_respond("200 Command okay.") + @ftp.help + @ftp.send(@method).should == "200" + + @server.should_receive(:help).and_respond("212 Directory status.") + @ftp.help + @ftp.send(@method).should == "212" + end +end diff --git a/spec/ruby/library/net-ftp/shared/list.rb b/spec/ruby/library/net-ftp/shared/list.rb new file mode 100644 index 0000000000..adc3fa59c1 --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/list.rb @@ -0,0 +1,104 @@ +describe :net_ftp_list, shared: true do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.passive = false + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + describe "when passed a block" do + it "yields each file in the list of files in the passed dir" do + expected = [ + "-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb", + "-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb", + "-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb" + ] + + res = [] + @ftp.send(@method, "test.folder") { |line| res << line} + res.should == expected + + @ftp.last_response.should == "226 transfer complete (LIST test.folder)\n" + end + end + + describe "when passed no block" do + it "returns an Array containing a list of files in the passed dir" do + expected = [ + "-rw-r--r-- 1 spec staff 507 17 Jul 18:41 last_response_code.rb", + "-rw-r--r-- 1 spec staff 50 17 Jul 18:41 list.rb", + "-rw-r--r-- 1 spec staff 48 17 Jul 18:41 pwd.rb" + ] + + @ftp.send(@method, "test.folder").should == expected + + @ftp.last_response.should == "226 transfer complete (LIST test.folder)\n" + end + end + + describe "when the LIST command fails" do + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:list).and_respond("450 Requested file action not taken..") + -> { @ftp.send(@method) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:list).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:list).and_respond("501 Syntax error, command unrecognized.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:list).and_respond("502 Command not implemented.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:list).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:list).and_respond("530 Not logged in.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + end + + describe "when opening the data port fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.") + @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.") + @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.") + @server.should_receive(:port).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:eprt).and_respond("530 Not logged in.") + @server.should_receive(:port).and_respond("530 Not logged in.") + -> { @ftp.send(@method) }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/shared/putbinaryfile.rb b/spec/ruby/library/net-ftp/shared/putbinaryfile.rb new file mode 100644 index 0000000000..45f53adc2a --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/putbinaryfile.rb @@ -0,0 +1,167 @@ +describe :net_ftp_putbinaryfile, shared: true do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @local_fixture_file = __dir__ + "/../fixtures/putbinaryfile" + @remote_tmp_file = tmp("binaryfile", false) + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + @ftp.binary = @binary_mode + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + + rm_r @remote_tmp_file + end + + it "sends the STOR command to the server" do + @ftp.send(@method, @local_fixture_file, "binary") + @ftp.last_response.should == "200 OK, Data received. (STOR binary)\n" + end + + it "sends the contents of the passed local_file, without modifications" do + @ftp.send(@method, @local_fixture_file, "binary") + + remote_lines = File.readlines(@remote_tmp_file) + local_lines = File.readlines(@local_fixture_file) + + remote_lines.should == local_lines + end + + it "returns nil" do + @ftp.send(@method, @local_fixture_file, "binary").should be_nil + end + + describe "when passed a block" do + it "yields the transmitted content as binary blocks of the passed size" do + res = [] + @ftp.send(@method, @local_fixture_file, "binary", 10) { |x| res << x } + res.should == [ + "This is an", " example f", + "ile\nwhich ", "is going t", + "o be trans", "mitted\nusi", + "ng #putbin", "aryfile.\n" + ] + end + end + + describe "when resuming an existing file" do + before :each do + File.open(@remote_tmp_file, "w") do |f| + f << "This is an example file\n" + end + + @ftp.resume = true + end + + it "sends the remaining content of the passed local_file to the passed remote_file" do + @ftp.send(@method, @local_fixture_file, "binary") + File.read(@remote_tmp_file).should == File.read(@local_fixture_file) + end + + describe "and the APPE command fails" do + it "raises a Net::FTPProtoError when the response code is 550" do + @server.should_receive(:appe).and_respond("Requested action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPProtoError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:appe).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:appe).and_respond("501 Syntax error, command unrecognized.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:appe).and_respond("502 Command not implemented.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:appe).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:appe).and_respond("530 Not logged in.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + end + end + + describe "when the STOR command fails" do + it "raises a Net::FTPPermError when the response code is 532" do + @server.should_receive(:stor).and_respond("532 Need account for storing files.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:stor).and_respond("450 Requested file action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPTempError when the response code is 452" do + @server.should_receive(:stor).and_respond("452 Requested action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 553" do + @server.should_receive(:stor).and_respond("553 Requested action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:stor).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:stor).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:stor).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:stor).and_respond("530 Not logged in.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + end + + describe "when opening the data port fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.") + @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.") + @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.") + @server.should_receive(:port).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:eprt).and_respond("530 Not logged in.") + @server.should_receive(:port).and_respond("530 Not logged in.") + -> { @ftp.send(@method, @local_fixture_file, "binary") }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/shared/puttextfile.rb b/spec/ruby/library/net-ftp/shared/puttextfile.rb new file mode 100644 index 0000000000..e2c0453352 --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/puttextfile.rb @@ -0,0 +1,128 @@ +describe :net_ftp_puttextfile, shared: true do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @local_fixture_file = __dir__ + "/../fixtures/puttextfile" + @remote_tmp_file = tmp("textfile", false) + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + @ftp.binary = @binary_mode + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + + rm_r @remote_tmp_file + end + + it "sends the STOR command to the server" do + @ftp.send(@method, @local_fixture_file, "text") + @ftp.last_response.should == "200 OK, Data received. (STOR text)\n" + end + + it "sends the contents of the passed local_file, using \\r\\n as the newline separator" do + @ftp.send(@method, @local_fixture_file, "text") + + remote_lines = File.binread(@remote_tmp_file) + local_lines = File.binread(@local_fixture_file) + + remote_lines.should_not == local_lines + remote_lines.should == local_lines.gsub("\n", "\r\n") + end + + guard -> { Net::FTP::VERSION < '0.3.6' } do + it "returns nil" do + @ftp.send(@method, @local_fixture_file, "text").should be_nil + end + end + + guard -> { Net::FTP::VERSION >= '0.3.6' } do + it "returns the response" do + @ftp.send(@method, @local_fixture_file, "text").should == @ftp.last_response + end + end + + describe "when passed a block" do + it "yields each transmitted line" do + res = [] + @ftp.send(@method, @local_fixture_file, "text") { |x| res << x } + res.should == [ + "This is an example file\r\n", + "which is going to be transmitted\r\n", + "using #puttextfile.\r\n" + ] + end + end + + describe "when the STOR command fails" do + it "raises a Net::FTPPermError when the response code is 532" do + @server.should_receive(:stor).and_respond("532 Need account for storing files.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 450" do + @server.should_receive(:stor).and_respond("450 Requested file action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPTempError when the response code is 452" do + @server.should_receive(:stor).and_respond("452 Requested action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 553" do + @server.should_receive(:stor).and_respond("553 Requested action not taken.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:stor).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:stor).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:stor).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:stor).and_respond("530 Not logged in.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + end + + describe "when opening the data port fails" do + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:eprt).and_respond("500 Syntax error, command unrecognized.") + @server.should_receive(:port).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:eprt).and_respond("501 Syntax error in parameters or arguments.") + @server.should_receive(:port).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:eprt).and_respond("421 Service not available, closing control connection.") + @server.should_receive(:port).and_respond("421 Service not available, closing control connection.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:eprt).and_respond("530 Not logged in.") + @server.should_receive(:port).and_respond("530 Not logged in.") + -> { @ftp.send(@method, @local_fixture_file, "text") }.should raise_error(Net::FTPPermError) + end + end +end diff --git a/spec/ruby/library/net-ftp/shared/pwd.rb b/spec/ruby/library/net-ftp/shared/pwd.rb new file mode 100644 index 0000000000..951d020f2d --- /dev/null +++ b/spec/ruby/library/net-ftp/shared/pwd.rb @@ -0,0 +1,3 @@ +describe :net_ftp_pwd, shared: true do + +end diff --git a/spec/ruby/library/net-ftp/site_spec.rb b/spec/ruby/library/net-ftp/site_spec.rb new file mode 100644 index 0000000000..c3e589a920 --- /dev/null +++ b/spec/ruby/library/net-ftp/site_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#site" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the SITE command with the passed argument to the server" do + @ftp.site("param") + @ftp.last_response.should == "200 Command okay. (SITE param)\n" + end + + it "returns nil" do + @ftp.site("param").should be_nil + end + + it "does not raise an error when the response code is 202" do + @server.should_receive(:site).and_respond("202 Command not implemented, superfluous at this site.") + -> { @ftp.site("param") }.should_not raise_error + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:site).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.site("param") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:site).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.site("param") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:site).and_respond("421 Service not available, closing control connection.") + -> { @ftp.site("param") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:site).and_respond("530 Requested action not taken.") + -> { @ftp.site("param") }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/size_spec.rb b/spec/ruby/library/net-ftp/size_spec.rb new file mode 100644 index 0000000000..0cf2e24477 --- /dev/null +++ b/spec/ruby/library/net-ftp/size_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#size" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the SIZE command to the server" do + @ftp.size("test.file") + @ftp.last_response.should == "213 1024\n" + end + + it "returns the size of the passed file as Integer" do + @ftp.size("test.file").should eql(1024) + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:size).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.size("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:size).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.size("test.file") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:size).and_respond("421 Service not available, closing control connection.") + -> { @ftp.size("test.file") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 550" do + @server.should_receive(:size).and_respond("550 Requested action not taken.") + -> { @ftp.size("test.file") }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/spec_helper.rb b/spec/ruby/library/net-ftp/spec_helper.rb new file mode 100644 index 0000000000..c87d16218b --- /dev/null +++ b/spec/ruby/library/net-ftp/spec_helper.rb @@ -0,0 +1,5 @@ +require "net/ftp" + +if defined?(Net::FTP.default_passive) + Net::FTP.default_passive = false +end diff --git a/spec/ruby/library/net-ftp/status_spec.rb b/spec/ruby/library/net-ftp/status_spec.rb new file mode 100644 index 0000000000..9d9f86c381 --- /dev/null +++ b/spec/ruby/library/net-ftp/status_spec.rb @@ -0,0 +1,67 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#status" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the STAT command to the server" do + @ftp.status + @ftp.last_response.should == "211 System status, or system help reply. (STAT)\n" + end + + it "sends the STAT command with an optional parameter to the server" do + @ftp.status("/pub").should == "211 System status, or system help reply. (STAT /pub)\n" + end + + it "returns the received information" do + @ftp.status.should == "211 System status, or system help reply. (STAT)\n" + end + + it "does not raise an error when the response code is 212" do + @server.should_receive(:stat).and_respond("212 Directory status.") + -> { @ftp.status }.should_not raise_error + end + + it "does not raise an error when the response code is 213" do + @server.should_receive(:stat).and_respond("213 File status.") + -> { @ftp.status }.should_not raise_error + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:stat).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.status }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:stat).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.status }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:stat).and_respond("502 Command not implemented.") + -> { @ftp.status }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:stat).and_respond("421 Service not available, closing control connection.") + -> { @ftp.status }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 530" do + @server.should_receive(:stat).and_respond("530 Requested action not taken.") + -> { @ftp.status }.should raise_error(Net::FTPPermError) + end +end diff --git a/spec/ruby/library/net-ftp/storbinary_spec.rb b/spec/ruby/library/net-ftp/storbinary_spec.rb new file mode 100644 index 0000000000..aa4c51f2e8 --- /dev/null +++ b/spec/ruby/library/net-ftp/storbinary_spec.rb @@ -0,0 +1,49 @@ +require_relative '../../spec_helper' + +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#storbinary" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @local_fixture_file = __dir__ + "/fixtures/putbinaryfile" + @tmp_file = tmp("binaryfile", false) + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + + rm_r @tmp_file + end + + it "sends the passed command and the passed File object's content to the server" do + File.open(@local_fixture_file) do |f| + f.binmode + + @ftp.storbinary("STOR binary", f, 4096) {} + @ftp.last_response.should == "200 OK, Data received. (STOR binary)\n" + end + end + + it "yields the transmitted content as binary blocks of the passed size" do + File.open(@local_fixture_file) do |f| + f.binmode + + res = [] + @ftp.storbinary("STOR binary", f, 10) { |x| res << x } + res.should == [ + "This is an", " example f", + "ile\nwhich ", "is going t", + "o be trans", "mitted\nusi", + "ng #putbin", "aryfile.\n" + ] + end + end +end diff --git a/spec/ruby/library/net-ftp/storlines_spec.rb b/spec/ruby/library/net-ftp/storlines_spec.rb new file mode 100644 index 0000000000..dc6830da7b --- /dev/null +++ b/spec/ruby/library/net-ftp/storlines_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' + +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#storlines" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @local_fixture_file = __dir__ + "/fixtures/puttextfile" + @tmp_file = tmp("textfile", false) + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + + rm_r @tmp_file + end + + it "sends the passed command and the passed File object's content to the server" do + File.open(@local_fixture_file) do |f| + @ftp.storlines("STOR text", f) {} + @ftp.last_response.should == "200 OK, Data received. (STOR text)\n" + end + end + + it "yields each line of the transmitted content" do + File.open(@local_fixture_file) do |f| + res = [] + @ftp.storlines("STOR text", f) { |x| res << x } + res.should == [ + "This is an example file\r\n", + "which is going to be transmitted\r\n", + "using #puttextfile.\r\n" + ] + end + end +end diff --git a/spec/ruby/library/net-ftp/system_spec.rb b/spec/ruby/library/net-ftp/system_spec.rb new file mode 100644 index 0000000000..2b7f0d2560 --- /dev/null +++ b/spec/ruby/library/net-ftp/system_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#system" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the SYST command to the server" do + @ftp.system + @ftp.last_response.should =~ /\A215 FTP Dummy Server \(SYST\)\Z/ + end + + it "returns the received information" do + @ftp.system.should =~ /\AFTP Dummy Server \(SYST\)\Z/ + end + + it "raises a Net::FTPPermError when the response code is 500" do + @server.should_receive(:syst).and_respond("500 Syntax error, command unrecognized.") + -> { @ftp.system }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 501" do + @server.should_receive(:syst).and_respond("501 Syntax error in parameters or arguments.") + -> { @ftp.system }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPPermError when the response code is 502" do + @server.should_receive(:syst).and_respond("502 Command not implemented.") + -> { @ftp.system }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPTempError when the response code is 421" do + @server.should_receive(:syst).and_respond("421 Service not available, closing control connection.") + -> { @ftp.system }.should raise_error(Net::FTPTempError) + end +end diff --git a/spec/ruby/library/net-ftp/voidcmd_spec.rb b/spec/ruby/library/net-ftp/voidcmd_spec.rb new file mode 100644 index 0000000000..f2536fe697 --- /dev/null +++ b/spec/ruby/library/net-ftp/voidcmd_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#voidcmd" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "sends the passed command to the server" do + @server.should_receive(:help).and_respond("2xx Does not raise.") + -> { @ftp.voidcmd("HELP") }.should_not raise_error + end + + it "returns nil" do + @server.should_receive(:help).and_respond("2xx Does not raise.") + @ftp.voidcmd("HELP").should be_nil + end + + it "raises a Net::FTPReplyError when the response code is 1xx" do + @server.should_receive(:help).and_respond("1xx Does raise a Net::FTPReplyError.") + -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPReplyError) + end + + it "raises a Net::FTPReplyError when the response code is 3xx" do + @server.should_receive(:help).and_respond("3xx Does raise a Net::FTPReplyError.") + -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPReplyError) + end + + it "raises a Net::FTPTempError when the response code is 4xx" do + @server.should_receive(:help).and_respond("4xx Does raise a Net::FTPTempError.") + -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPTempError) + end + + it "raises a Net::FTPPermError when the response code is 5xx" do + @server.should_receive(:help).and_respond("5xx Does raise a Net::FTPPermError.") + -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPPermError) + end + + it "raises a Net::FTPProtoError when the response code is not valid" do + @server.should_receive(:help).and_respond("999 Does raise a Net::FTPProtoError.") + -> { @ftp.voidcmd("HELP") }.should raise_error(Net::FTPProtoError) + end +end diff --git a/spec/ruby/library/net-ftp/welcome_spec.rb b/spec/ruby/library/net-ftp/welcome_spec.rb new file mode 100644 index 0000000000..4279127ce3 --- /dev/null +++ b/spec/ruby/library/net-ftp/welcome_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'spec_helper' +require_relative 'fixtures/server' + +describe "Net::FTP#welcome" do + before :each do + @server = NetFTPSpecs::DummyFTP.new + @server.serve_once + + @ftp = Net::FTP.new + @ftp.connect(@server.hostname, @server.server_port) + end + + after :each do + @ftp.quit rescue nil + @ftp.close + @server.stop + end + + it "returns the server's welcome message" do + @ftp.welcome.should be_nil + @ftp.login + @ftp.welcome.should == "230 User logged in, proceed. (USER anonymous)\n" + end +end diff --git a/spec/ruby/library/net-http/HTTPBadResponse_spec.rb b/spec/ruby/library/net-http/HTTPBadResponse_spec.rb new file mode 100644 index 0000000000..8f2e8ccfaf --- /dev/null +++ b/spec/ruby/library/net-http/HTTPBadResponse_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPBadResponse" do + it "is a subclass of StandardError" do + Net::HTTPBadResponse.should < StandardError + end +end diff --git a/spec/ruby/library/net-http/HTTPClientExcepton_spec.rb b/spec/ruby/library/net-http/HTTPClientExcepton_spec.rb new file mode 100644 index 0000000000..2992e6596f --- /dev/null +++ b/spec/ruby/library/net-http/HTTPClientExcepton_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPClientException" do + it "is a subclass of Net::ProtoServerError" do + Net::HTTPClientException.should < Net::ProtoServerError + end + + it "includes the Net::HTTPExceptions module" do + Net::HTTPClientException.should < Net::HTTPExceptions + end +end diff --git a/spec/ruby/library/net-http/HTTPError_spec.rb b/spec/ruby/library/net-http/HTTPError_spec.rb new file mode 100644 index 0000000000..7f79eef8cf --- /dev/null +++ b/spec/ruby/library/net-http/HTTPError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPError" do + it "is a subclass of Net::ProtocolError" do + Net::HTTPError.should < Net::ProtocolError + end + + it "includes the Net::HTTPExceptions module" do + Net::HTTPError.should < Net::HTTPExceptions + end +end diff --git a/spec/ruby/library/net-http/HTTPFatalError_spec.rb b/spec/ruby/library/net-http/HTTPFatalError_spec.rb new file mode 100644 index 0000000000..0113b9da2d --- /dev/null +++ b/spec/ruby/library/net-http/HTTPFatalError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPFatalError" do + it "is a subclass of Net::ProtoFatalError" do + Net::HTTPFatalError.should < Net::ProtoFatalError + end + + it "includes the Net::HTTPExceptions module" do + Net::HTTPFatalError.should < Net::HTTPExceptions + end +end diff --git a/spec/ruby/library/net-http/HTTPHeaderSyntaxError_spec.rb b/spec/ruby/library/net-http/HTTPHeaderSyntaxError_spec.rb new file mode 100644 index 0000000000..b3b73ff46f --- /dev/null +++ b/spec/ruby/library/net-http/HTTPHeaderSyntaxError_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPHeaderSyntaxError" do + it "is a subclass of StandardError" do + Net::HTTPHeaderSyntaxError.should < StandardError + end +end diff --git a/spec/ruby/library/net-http/HTTPRetriableError_spec.rb b/spec/ruby/library/net-http/HTTPRetriableError_spec.rb new file mode 100644 index 0000000000..677731fb68 --- /dev/null +++ b/spec/ruby/library/net-http/HTTPRetriableError_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPRetriableError" do + it "is a subclass of Net::ProtoRetriableError" do + Net::HTTPRetriableError.should < Net::ProtoRetriableError + end + + it "includes the Net::HTTPExceptions module" do + Net::HTTPRetriableError.should < Net::HTTPExceptions + end +end diff --git a/spec/ruby/library/net-http/HTTPServerException_spec.rb b/spec/ruby/library/net-http/HTTPServerException_spec.rb new file mode 100644 index 0000000000..020d3cce85 --- /dev/null +++ b/spec/ruby/library/net-http/HTTPServerException_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'net/http' + +describe "Net::HTTPServerException" do + it "is a subclass of Net::ProtoServerError and is warned as deprecated" do + -> { eval("Net::HTTPServerException").should < Net::ProtoServerError }.should complain(/warning: constant Net::HTTPServerException is deprecated/) + end + + it "includes the Net::HTTPExceptions module and is warned as deprecated" do + -> { eval("Net::HTTPServerException").should < Net::HTTPExceptions }.should complain(/warning: constant Net::HTTPServerException is deprecated/) + end +end diff --git a/spec/ruby/library/net-http/http/Proxy_spec.rb b/spec/ruby/library/net-http/http/Proxy_spec.rb new file mode 100644 index 0000000000..a1a04fa00b --- /dev/null +++ b/spec/ruby/library/net-http/http/Proxy_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.Proxy" do + it "returns a new subclass of Net::HTTP" do + Net::HTTP.Proxy("localhost").should < Net::HTTP + end + + it "returns Net::HTTP when the passed address is nil" do + Net::HTTP.Proxy(nil).should == Net::HTTP + end + + it "sets the returned subclasses' proxy options based on the passed arguments" do + http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks") + http_with_proxy.proxy_address.should == "localhost" + http_with_proxy.proxy_port.should eql(1234) + http_with_proxy.proxy_user.should == "rspec" + http_with_proxy.proxy_pass.should == "rocks" + end +end + +describe "Net::HTTP#proxy?" do + describe "when self is no proxy class instance" do + it "returns false" do + Net::HTTP.new("localhost", 3333).proxy?.should be_false + end + end + + describe "when self is a proxy class instance" do + it "returns false" do + http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks") + http_with_proxy.new("localhost", 3333).proxy?.should be_true + end + end +end diff --git a/spec/ruby/library/net-http/http/active_spec.rb b/spec/ruby/library/net-http/http/active_spec.rb new file mode 100644 index 0000000000..c260274594 --- /dev/null +++ b/spec/ruby/library/net-http/http/active_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/started' + +describe "Net::HTTP#active?" do + it_behaves_like :net_http_started_p, :active? +end diff --git a/spec/ruby/library/net-http/http/address_spec.rb b/spec/ruby/library/net-http/http/address_spec.rb new file mode 100644 index 0000000000..7c5b82a8f9 --- /dev/null +++ b/spec/ruby/library/net-http/http/address_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#address" do + it "returns the current host name" do + net = Net::HTTP.new("localhost") + net.address.should == "localhost" + end +end diff --git a/spec/ruby/library/net-http/http/close_on_empty_response_spec.rb b/spec/ruby/library/net-http/http/close_on_empty_response_spec.rb new file mode 100644 index 0000000000..9cc756befb --- /dev/null +++ b/spec/ruby/library/net-http/http/close_on_empty_response_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#close_on_empty_response" do + it "needs to be reviewed for spec completeness" +end + +describe "Net::HTTP#close_on_empty_response=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/net-http/http/copy_spec.rb b/spec/ruby/library/net-http/http/copy_spec.rb new file mode 100644 index 0000000000..fba96c0f11 --- /dev/null +++ b/spec/ruby/library/net-http/http/copy_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#copy" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a COPY request to the passed path and returns the response" do + response = @http.copy("/request") + response.should be_kind_of(Net::HTTPResponse) + response.body.should == "Request type: COPY" + end +end diff --git a/spec/ruby/library/net-http/http/default_port_spec.rb b/spec/ruby/library/net-http/http/default_port_spec.rb new file mode 100644 index 0000000000..95b7316a0c --- /dev/null +++ b/spec/ruby/library/net-http/http/default_port_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.default_port" do + it "returns 80" do + Net::HTTP.http_default_port.should eql(80) + end +end diff --git a/spec/ruby/library/net-http/http/delete_spec.rb b/spec/ruby/library/net-http/http/delete_spec.rb new file mode 100644 index 0000000000..d73aa5b375 --- /dev/null +++ b/spec/ruby/library/net-http/http/delete_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#delete" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a DELETE request to the passed path and returns the response" do + response = @http.delete("/request") + response.should be_kind_of(Net::HTTPResponse) + response.body.should == "Request type: DELETE" + end +end diff --git a/spec/ruby/library/net-http/http/finish_spec.rb b/spec/ruby/library/net-http/http/finish_spec.rb new file mode 100644 index 0000000000..d4aa00dffe --- /dev/null +++ b/spec/ruby/library/net-http/http/finish_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#finish" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.new("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + describe "when self has been started" do + it "closes the tcp connection" do + @http.start + @http.finish + @http.started?.should be_false + end + end + + describe "when self has not been started yet" do + it "raises an IOError" do + -> { @http.finish }.should raise_error(IOError) + end + end +end diff --git a/spec/ruby/library/net-http/http/fixtures/http_server.rb b/spec/ruby/library/net-http/http/fixtures/http_server.rb new file mode 100644 index 0000000000..c1cedbfa76 --- /dev/null +++ b/spec/ruby/library/net-http/http/fixtures/http_server.rb @@ -0,0 +1,123 @@ +require 'socket' + +module NetHTTPSpecs + class NullWriter + def <<(s) end + def puts(*args) end + def print(*args) end + def printf(*args) end + end + + class SmallHTTPServer + def initialize(bind_address) + @server = TCPServer.new(bind_address, 0) + @thread = Thread.new { + Thread.current.abort_on_exception = true + listen + } + end + + def ip + @server.addr[3] + end + + def port + @server.addr[1] + end + + def listen + until @server.closed? + client = @server.accept + handle_client(client) + end + end + + def handle_client(client) + begin + until client.closed? + request = client.gets("\r\n\r\n") + break unless request + if request == "CLOSE" + @server.close + break + end + handle_request(client, request) + end + ensure + client.close + end + end + + def parse_request(request) + request, *headers = request.chomp.lines.map { |line| line.chomp } + request_method, request_uri, _http_version = request.split + headers = headers.map { |line| line.split(': ', 2) }.to_h + [request_method, request_uri, headers] + end + + def handle_request(client, request) + request_method, request_uri, headers = parse_request(request) + + if headers.include? 'Content-Length' + request_body_size = Integer(headers['Content-Length']) + request_body = client.read(request_body_size) + end + + case request_uri + when '/' + raise request_method unless request_method == 'GET' + reply(client, "This is the index page.", request_method) + when '/request' + reply(client, "Request type: #{request_method}", request_method) + when '/request/body' + reply(client, request_body, request_method) + when '/request/header' + reply(client, headers.inspect, request_method) + when '/request/basic_auth' + reply(client, "username: \npassword: ", request_method) + else + raise request_uri + end + end + + def reply(client, body, request_method) + client.print "HTTP/1.1 200 OK\r\n" + if request_method == 'HEAD' + client.close + else + client.print "Content-Type: text/plain\r\n" + client.print "Content-Length: #{body.bytesize}\r\n" + client.print "\r\n" + client.print body + end + end + + def close + TCPSocket.open(ip, port) do |socket| + socket.write "CLOSE" + end + @thread.join + end + end + + @server = nil + + class << self + def port + raise "server not started" unless @server + @server.port + end + + def start_server + bind_address = platform_is(:windows) ? "localhost" : "127.0.0.1" + @server = SmallHTTPServer.new(bind_address) + end + + def stop_server + if @server + @server.close + @server = nil + end + end + end +end diff --git a/spec/ruby/library/net-http/http/get2_spec.rb b/spec/ruby/library/net-http/http/get2_spec.rb new file mode 100644 index 0000000000..57c05ec64b --- /dev/null +++ b/spec/ruby/library/net-http/http/get2_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_get' + +describe "Net::HTTP#get2" do + it_behaves_like :net_http_request_get, :get2 +end diff --git a/spec/ruby/library/net-http/http/get_print_spec.rb b/spec/ruby/library/net-http/http/get_print_spec.rb new file mode 100644 index 0000000000..3c24ce44ea --- /dev/null +++ b/spec/ruby/library/net-http/http/get_print_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP.get_print" do + before :each do + NetHTTPSpecs.start_server + @port = NetHTTPSpecs.port + end + + after :each do + NetHTTPSpecs.stop_server + end + + describe "when passed URI" do + it "it prints the body of the specified uri to $stdout" do + -> do + Net::HTTP.get_print URI.parse("http://localhost:#{@port}/") + end.should output(/This is the index page\./) + end + end + + describe "when passed host, path, port" do + it "it prints the body of the specified uri to $stdout" do + -> do + Net::HTTP.get_print 'localhost', "/", @port + end.should output(/This is the index page\./) + end + end +end diff --git a/spec/ruby/library/net-http/http/get_response_spec.rb b/spec/ruby/library/net-http/http/get_response_spec.rb new file mode 100644 index 0000000000..7133ef8101 --- /dev/null +++ b/spec/ruby/library/net-http/http/get_response_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP.get_response" do + before :each do + NetHTTPSpecs.start_server + @port = NetHTTPSpecs.port + end + + after :each do + NetHTTPSpecs.stop_server + end + + describe "when passed URI" do + it "returns the response for the specified uri" do + res = Net::HTTP.get_response(URI.parse("http://localhost:#{@port}/")) + res.content_type.should == "text/plain" + res.body.should == "This is the index page." + end + end + + describe "when passed host, path, port" do + it "returns the response for the specified host-path-combination" do + res = Net::HTTP.get_response('localhost', "/", @port) + res.content_type.should == "text/plain" + res.body.should == "This is the index page." + end + end +end diff --git a/spec/ruby/library/net-http/http/get_spec.rb b/spec/ruby/library/net-http/http/get_spec.rb new file mode 100644 index 0000000000..e64a61c52c --- /dev/null +++ b/spec/ruby/library/net-http/http/get_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP.get" do + before :each do + NetHTTPSpecs.start_server + @port = NetHTTPSpecs.port + end + + after :each do + NetHTTPSpecs.stop_server + end + + describe "when passed URI" do + it "returns the body of the specified uri" do + Net::HTTP.get(URI.parse("http://localhost:#{@port}/")).should == "This is the index page." + end + end + + describe "when passed host, path, port" do + it "returns the body of the specified host-path-combination" do + Net::HTTP.get('localhost', "/", @port).should == "This is the index page." + end + end +end + +quarantine! do # These specs fail frequently with CHECK_LEAKS=true +describe "Net::HTTP.get" do + describe "when reading gzipped contents" do + def start_threads + require 'zlib' + require 'stringio' + + server = nil + server_thread = Thread.new do + server = TCPServer.new("127.0.0.1", 0) + begin + c = server.accept + ensure + server.close + end + c.print "HTTP/1.1 200\r\n" + c.print "Content-Type: text/plain\r\n" + c.print "Content-Encoding: gzip\r\n" + s = StringIO.new + z = Zlib::GzipWriter.new(s) + begin + z.write 'Hello World!' + ensure + z.close + end + c.print "Content-Length: #{s.length}\r\n\r\n" + # Write partial gzip content + c.write s.string.byteslice(0..-2) + c.flush + c + end + Thread.pass until server && server_thread.stop? + + client_thread = Thread.new do + Thread.current.report_on_exception = false + Net::HTTP.get("127.0.0.1", '/', server.connect_address.ip_port) + end + + socket = server_thread.value + Thread.pass until client_thread.stop? + + [socket, client_thread] + end + + it "propagates exceptions interrupting the thread and does not replace it with Zlib::BufError" do + my_exception = Class.new(RuntimeError) + socket, client_thread = start_threads + begin + client_thread.raise my_exception, "my exception" + -> { client_thread.value }.should raise_error(my_exception) + ensure + socket.close + end + end + + it "lets the kill Thread exception goes through and does not replace it with Zlib::BufError" do + socket, client_thread = start_threads + begin + client_thread.kill + client_thread.value.should == nil + ensure + socket.close + end + end + end +end +end diff --git a/spec/ruby/library/net-http/http/head2_spec.rb b/spec/ruby/library/net-http/http/head2_spec.rb new file mode 100644 index 0000000000..84cfff33d7 --- /dev/null +++ b/spec/ruby/library/net-http/http/head2_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_head' + +describe "Net::HTTP#head2" do + it_behaves_like :net_http_request_head, :head2 +end diff --git a/spec/ruby/library/net-http/http/head_spec.rb b/spec/ruby/library/net-http/http/head_spec.rb new file mode 100644 index 0000000000..64621fa87b --- /dev/null +++ b/spec/ruby/library/net-http/http/head_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#head" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a HEAD request to the passed path and returns the response" do + response = @http.head("/request") + # HEAD requests have no responses + response.body.should be_nil + end + + it "returns a Net::HTTPResponse" do + @http.head("/request").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/http_default_port_spec.rb b/spec/ruby/library/net-http/http/http_default_port_spec.rb new file mode 100644 index 0000000000..3b17bcd0a5 --- /dev/null +++ b/spec/ruby/library/net-http/http/http_default_port_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.http_default_port" do + it "returns 80" do + Net::HTTP.http_default_port.should eql(80) + end +end diff --git a/spec/ruby/library/net-http/http/https_default_port_spec.rb b/spec/ruby/library/net-http/http/https_default_port_spec.rb new file mode 100644 index 0000000000..8c24e1d97c --- /dev/null +++ b/spec/ruby/library/net-http/http/https_default_port_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.https_default_port" do + it "returns 443" do + Net::HTTP.https_default_port.should eql(443) + end +end diff --git a/spec/ruby/library/net-http/http/initialize_spec.rb b/spec/ruby/library/net-http/http/initialize_spec.rb new file mode 100644 index 0000000000..78aa01e1aa --- /dev/null +++ b/spec/ruby/library/net-http/http/initialize_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#initialize" do + it "is private" do + Net::HTTP.should have_private_instance_method(:initialize) + end + + describe "when passed address" do + before :each do + @net = Net::HTTP.allocate + @net.send(:initialize, "localhost") + end + + it "sets the new Net::HTTP instance's address to the passed address" do + @net.address.should == "localhost" + end + + it "sets the new Net::HTTP instance's port to the default HTTP port" do + @net.port.should eql(Net::HTTP.default_port) + end + + it "does not start the new Net::HTTP instance" do + @net.started?.should be_false + end + end + + describe "when passed address, port" do + before :each do + @net = Net::HTTP.allocate + @net.send(:initialize, "localhost", 3333) + end + + it "sets the new Net::HTTP instance's address to the passed address" do + @net.address.should == "localhost" + end + + it "sets the new Net::HTTP instance's port to the passed port" do + @net.port.should eql(3333) + end + + it "does not start the new Net::HTTP instance" do + @net.started?.should be_false + end + end +end diff --git a/spec/ruby/library/net-http/http/inspect_spec.rb b/spec/ruby/library/net-http/http/inspect_spec.rb new file mode 100644 index 0000000000..b8f650809e --- /dev/null +++ b/spec/ruby/library/net-http/http/inspect_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#inspect" do + before :each do + NetHTTPSpecs.start_server + @port = NetHTTPSpecs.port + @http = Net::HTTP.new("localhost", @port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "returns a String representation of self" do + @http.inspect.should be_kind_of(String) + @http.inspect.should == "#<Net::HTTP localhost:#{@port} open=false>" + + @http.start + @http.inspect.should == "#<Net::HTTP localhost:#{@port} open=true>" + end +end diff --git a/spec/ruby/library/net-http/http/is_version_1_1_spec.rb b/spec/ruby/library/net-http/http/is_version_1_1_spec.rb new file mode 100644 index 0000000000..bdb343f9e0 --- /dev/null +++ b/spec/ruby/library/net-http/http/is_version_1_1_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'shared/version_1_1' + +describe "Net::HTTP.is_version_1_1?" do + it_behaves_like :net_http_version_1_1_p, :is_version_1_1? +end diff --git a/spec/ruby/library/net-http/http/is_version_1_2_spec.rb b/spec/ruby/library/net-http/http/is_version_1_2_spec.rb new file mode 100644 index 0000000000..555bb205dd --- /dev/null +++ b/spec/ruby/library/net-http/http/is_version_1_2_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'shared/version_1_2' + +describe "Net::HTTP.is_version_1_2?" do + it_behaves_like :net_http_version_1_2_p, :is_version_1_2? +end diff --git a/spec/ruby/library/net-http/http/lock_spec.rb b/spec/ruby/library/net-http/http/lock_spec.rb new file mode 100644 index 0000000000..aa1f944196 --- /dev/null +++ b/spec/ruby/library/net-http/http/lock_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#lock" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a LOCK request to the passed path and returns the response" do + response = @http.lock("/request", "test=test") + response.should be_kind_of(Net::HTTPResponse) + response.body.should == "Request type: LOCK" + end +end diff --git a/spec/ruby/library/net-http/http/mkcol_spec.rb b/spec/ruby/library/net-http/http/mkcol_spec.rb new file mode 100644 index 0000000000..f8009f9059 --- /dev/null +++ b/spec/ruby/library/net-http/http/mkcol_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#mkcol" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a MKCOL request to the passed path and returns the response" do + response = @http.mkcol("/request") + response.should be_kind_of(Net::HTTPResponse) + response.body.should == "Request type: MKCOL" + end +end diff --git a/spec/ruby/library/net-http/http/move_spec.rb b/spec/ruby/library/net-http/http/move_spec.rb new file mode 100644 index 0000000000..ae43016a2c --- /dev/null +++ b/spec/ruby/library/net-http/http/move_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#head" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a MOVE request to the passed path and returns the response" do + response = @http.move("/request") + # HEAD requests have no responses + response.body.should == "Request type: MOVE" + end + + it "returns a Net::HTTPResponse" do + @http.move("/request").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/new_spec.rb b/spec/ruby/library/net-http/http/new_spec.rb new file mode 100644 index 0000000000..1ec6bbd0c0 --- /dev/null +++ b/spec/ruby/library/net-http/http/new_spec.rb @@ -0,0 +1,86 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.new" do + describe "when passed address" do + before :each do + @http = Net::HTTP.new("localhost") + end + + it "returns a Net::HTTP instance" do + @http.proxy?.should be_false + @http.instance_of?(Net::HTTP).should be_true + end + + it "sets the new Net::HTTP instance's address to the passed address" do + @http.address.should == "localhost" + end + + it "sets the new Net::HTTP instance's port to the default HTTP port" do + @http.port.should eql(Net::HTTP.default_port) + end + + it "does not start the new Net::HTTP instance" do + @http.started?.should be_false + end + end + + describe "when passed address, port" do + before :each do + @http = Net::HTTP.new("localhost", 3333) + end + + it "returns a Net::HTTP instance" do + @http.proxy?.should be_false + @http.instance_of?(Net::HTTP).should be_true + end + + it "sets the new Net::HTTP instance's address to the passed address" do + @http.address.should == "localhost" + end + + it "sets the new Net::HTTP instance's port to the passed port" do + @http.port.should eql(3333) + end + + it "does not start the new Net::HTTP instance" do + @http.started?.should be_false + end + end + + describe "when passed address, port, *proxy_options" do + it "returns a Net::HTTP instance" do + http = Net::HTTP.new("localhost", 3333, "localhost") + http.proxy?.should be_true + http.instance_of?(Net::HTTP).should be_true + http.should be_kind_of(Net::HTTP) + end + + it "correctly sets the passed Proxy options" do + http = Net::HTTP.new("localhost", 3333, "localhost") + http.proxy_address.should == "localhost" + http.proxy_port.should eql(80) + http.proxy_user.should be_nil + http.proxy_pass.should be_nil + + http = Net::HTTP.new("localhost", 3333, "localhost", 1234) + http.proxy_address.should == "localhost" + http.proxy_port.should eql(1234) + http.proxy_user.should be_nil + http.proxy_pass.should be_nil + + http = Net::HTTP.new("localhost", 3333, "localhost", 1234, "rubyspec") + http.proxy_address.should == "localhost" + http.proxy_port.should eql(1234) + http.proxy_user.should == "rubyspec" + http.proxy_pass.should be_nil + + http = Net::HTTP.new("localhost", 3333, "localhost", 1234, "rubyspec", "rocks") + http.proxy_address.should == "localhost" + http.proxy_port.should eql(1234) + http.proxy_user.should == "rubyspec" + http.proxy_pass.should == "rocks" + end + end + +end diff --git a/spec/ruby/library/net-http/http/newobj_spec.rb b/spec/ruby/library/net-http/http/newobj_spec.rb new file mode 100644 index 0000000000..e19b30fca9 --- /dev/null +++ b/spec/ruby/library/net-http/http/newobj_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.newobj" do + before :each do + @net = Net::HTTP.newobj("localhost") + end + + describe "when passed address" do + it "returns a new Net::HTTP instance" do + @net.should be_kind_of(Net::HTTP) + end + + it "sets the new Net::HTTP instance's address to the passed address" do + @net.address.should == "localhost" + end + + it "sets the new Net::HTTP instance's port to the default HTTP port" do + @net.port.should eql(Net::HTTP.default_port) + end + + it "does not start the new Net::HTTP instance" do + @net.started?.should be_false + end + end + + describe "when passed address, port" do + before :each do + @net = Net::HTTP.newobj("localhost", 3333) + end + + it "returns a new Net::HTTP instance" do + @net.should be_kind_of(Net::HTTP) + end + + it "sets the new Net::HTTP instance's address to the passed address" do + @net.address.should == "localhost" + end + + it "sets the new Net::HTTP instance's port to the passed port" do + @net.port.should eql(3333) + end + + it "does not start the new Net::HTTP instance" do + @net.started?.should be_false + end + end +end diff --git a/spec/ruby/library/net-http/http/open_timeout_spec.rb b/spec/ruby/library/net-http/http/open_timeout_spec.rb new file mode 100644 index 0000000000..0d93752271 --- /dev/null +++ b/spec/ruby/library/net-http/http/open_timeout_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#open_timeout" do + it "returns the seconds to wait till the connection is open" do + net = Net::HTTP.new("localhost") + net.open_timeout.should eql(60) + net.open_timeout = 10 + net.open_timeout.should eql(10) + end +end + +describe "Net::HTTP#open_timeout=" do + it "sets the seconds to wait till the connection is open" do + net = Net::HTTP.new("localhost") + net.open_timeout = 10 + net.open_timeout.should eql(10) + end + + it "returns the newly set value" do + net = Net::HTTP.new("localhost") + (net.open_timeout = 10).should eql(10) + end +end diff --git a/spec/ruby/library/net-http/http/options_spec.rb b/spec/ruby/library/net-http/http/options_spec.rb new file mode 100644 index 0000000000..3d9887a557 --- /dev/null +++ b/spec/ruby/library/net-http/http/options_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#options" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends an options request to the passed path and returns the response" do + response = @http.options("/request") + + response.body.should == "Request type: OPTIONS" + end + + it "returns a Net::HTTPResponse" do + @http.options("/request").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/port_spec.rb b/spec/ruby/library/net-http/http/port_spec.rb new file mode 100644 index 0000000000..0984d5e6ce --- /dev/null +++ b/spec/ruby/library/net-http/http/port_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#port" do + it "returns the current port number" do + net = Net::HTTP.new("localhost", 3333) + net.port.should eql(3333) + end +end diff --git a/spec/ruby/library/net-http/http/post2_spec.rb b/spec/ruby/library/net-http/http/post2_spec.rb new file mode 100644 index 0000000000..abc998709f --- /dev/null +++ b/spec/ruby/library/net-http/http/post2_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_post' + +describe "Net::HTTP#post2" do + it_behaves_like :net_http_request_post, :post2 +end diff --git a/spec/ruby/library/net-http/http/post_form_spec.rb b/spec/ruby/library/net-http/http/post_form_spec.rb new file mode 100644 index 0000000000..de64a25bae --- /dev/null +++ b/spec/ruby/library/net-http/http/post_form_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP.post_form when passed URI" do + before :each do + NetHTTPSpecs.start_server + @port = NetHTTPSpecs.port + end + + after :each do + NetHTTPSpecs.stop_server + end + + it "POSTs the passed form data to the given uri" do + uri = URI.parse("http://localhost:#{@port}/request/body") + data = { test: :data } + + res = Net::HTTP.post_form(uri, data) + res.body.should == "test=data" + end +end diff --git a/spec/ruby/library/net-http/http/post_spec.rb b/spec/ruby/library/net-http/http/post_spec.rb new file mode 100644 index 0000000000..b8b8d16ad1 --- /dev/null +++ b/spec/ruby/library/net-http/http/post_spec.rb @@ -0,0 +1,76 @@ +require_relative '../../../spec_helper' +require 'net/http' +require 'uri' +require_relative 'fixtures/http_server' + +describe "Net::HTTP.post" do + before :each do + NetHTTPSpecs.start_server + end + + after :each do + NetHTTPSpecs.stop_server + end + + it "sends post request to the specified URI and returns response" do + response = Net::HTTP.post( + URI("http://localhost:#{NetHTTPSpecs.port}/request"), + '{ "q": "ruby", "max": "50" }', + "Content-Type" => "application/json") + response.body.should == "Request type: POST" + end + + it "returns a Net::HTTPResponse" do + response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request"), "test=test") + response.should be_kind_of(Net::HTTPResponse) + end + + ruby_version_is ""..."4.0" do + it "sends Content-Type: application/x-www-form-urlencoded by default" do + response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test") + response.body.should include({ "Content-Type" => "application/x-www-form-urlencoded" }.inspect.delete("{}")) + end + end + + it "does not support HTTP Basic Auth" do + response = Net::HTTP.post( + URI("http://john:qwerty@localhost:#{NetHTTPSpecs.port}/request/basic_auth"), + "test=test") + response.body.should == "username: \npassword: " + end +end + +describe "Net::HTTP#post" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends an post request to the passed path and returns the response" do + response = @http.post("/request", "test=test") + response.body.should == "Request type: POST" + end + + it "returns a Net::HTTPResponse" do + @http.post("/request", "test=test").should be_kind_of(Net::HTTPResponse) + end + + describe "when passed a block" do + it "yields fragments of the response body to the passed block" do + str = +"" + @http.post("/request", "test=test") do |res| + str << res + end + str.should == "Request type: POST" + end + + it "returns a Net::HTTPResponse" do + @http.post("/request", "test=test") {}.should be_kind_of(Net::HTTPResponse) + end + end +end diff --git a/spec/ruby/library/net-http/http/propfind_spec.rb b/spec/ruby/library/net-http/http/propfind_spec.rb new file mode 100644 index 0000000000..f3742d1b1a --- /dev/null +++ b/spec/ruby/library/net-http/http/propfind_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#propfind" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends an propfind request to the passed path and returns the response" do + response = @http.propfind("/request", "test=test") + response.body.should == "Request type: PROPFIND" + end + + it "returns a Net::HTTPResponse" do + @http.propfind("/request", "test=test").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/proppatch_spec.rb b/spec/ruby/library/net-http/http/proppatch_spec.rb new file mode 100644 index 0000000000..0163d24d46 --- /dev/null +++ b/spec/ruby/library/net-http/http/proppatch_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#proppatch" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends an proppatch request to the passed path and returns the response" do + response = @http.proppatch("/request", "test=test") + response.body.should == "Request type: PROPPATCH" + end + + it "returns a Net::HTTPResponse" do + @http.proppatch("/request", "test=test").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/proxy_address_spec.rb b/spec/ruby/library/net-http/http/proxy_address_spec.rb new file mode 100644 index 0000000000..5b5efb7ac0 --- /dev/null +++ b/spec/ruby/library/net-http/http/proxy_address_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.proxy_address" do + describe "when self is no proxy class" do + it "returns nil" do + Net::HTTP.proxy_address.should be_nil + end + end + + describe "when self is a proxy class" do + it "returns the address for self's proxy connection" do + Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_address.should == "localhost" + end + end +end + +describe "Net::HTTP#proxy_address" do + describe "when self is no proxy class instance" do + it "returns nil" do + Net::HTTP.new("localhost", 3333).proxy_address.should be_nil + end + end + + describe "when self is a proxy class instance" do + it "returns the password for self's proxy connection" do + http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks") + http_with_proxy.new("localhost", 3333).proxy_address.should == "localhost" + end + end +end diff --git a/spec/ruby/library/net-http/http/proxy_class_spec.rb b/spec/ruby/library/net-http/http/proxy_class_spec.rb new file mode 100644 index 0000000000..00975aef4e --- /dev/null +++ b/spec/ruby/library/net-http/http/proxy_class_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.proxy_class?" do + it "returns true if self is a class created with Net::HTTP.Proxy" do + Net::HTTP.proxy_class?.should be_false + Net::HTTP.Proxy("localhost").proxy_class?.should be_true + end +end diff --git a/spec/ruby/library/net-http/http/proxy_pass_spec.rb b/spec/ruby/library/net-http/http/proxy_pass_spec.rb new file mode 100644 index 0000000000..4e393a53ff --- /dev/null +++ b/spec/ruby/library/net-http/http/proxy_pass_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.proxy_pass" do + describe "when self is no proxy class" do + it "returns nil" do + Net::HTTP.proxy_pass.should be_nil + end + end + + describe "when self is a proxy class" do + it "returns nil if no password was set for self's proxy connection" do + Net::HTTP.Proxy("localhost").proxy_pass.should be_nil + end + + it "returns the password for self's proxy connection" do + Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_pass.should == "rocks" + end + end +end + +describe "Net::HTTP#proxy_pass" do + describe "when self is no proxy class instance" do + it "returns nil" do + Net::HTTP.new("localhost", 3333).proxy_pass.should be_nil + end + end + + describe "when self is a proxy class instance" do + it "returns nil if no password was set for self's proxy connection" do + Net::HTTP.Proxy("localhost").new("localhost", 3333).proxy_pass.should be_nil + end + + it "returns the password for self's proxy connection" do + http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks") + http_with_proxy.new("localhost", 3333).proxy_pass.should == "rocks" + end + end +end diff --git a/spec/ruby/library/net-http/http/proxy_port_spec.rb b/spec/ruby/library/net-http/http/proxy_port_spec.rb new file mode 100644 index 0000000000..d7d37f3927 --- /dev/null +++ b/spec/ruby/library/net-http/http/proxy_port_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.proxy_port" do + describe "when self is no proxy class" do + it "returns nil" do + Net::HTTP.proxy_port.should be_nil + end + end + + describe "when self is a proxy class" do + it "returns 80 if no port was set for self's proxy connection" do + Net::HTTP.Proxy("localhost").proxy_port.should eql(80) + end + + it "returns the port for self's proxy connection" do + Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_port.should eql(1234) + end + end +end + +describe "Net::HTTP#proxy_port" do + describe "when self is no proxy class instance" do + it "returns nil" do + Net::HTTP.new("localhost", 3333).proxy_port.should be_nil + end + end + + describe "when self is a proxy class instance" do + it "returns 80 if no port was set for self's proxy connection" do + Net::HTTP.Proxy("localhost").new("localhost", 3333).proxy_port.should eql(80) + end + + it "returns the port for self's proxy connection" do + http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks") + http_with_proxy.new("localhost", 3333).proxy_port.should eql(1234) + end + end +end diff --git a/spec/ruby/library/net-http/http/proxy_user_spec.rb b/spec/ruby/library/net-http/http/proxy_user_spec.rb new file mode 100644 index 0000000000..ef7654425d --- /dev/null +++ b/spec/ruby/library/net-http/http/proxy_user_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.proxy_user" do + describe "when self is no proxy class" do + it "returns nil" do + Net::HTTP.proxy_user.should be_nil + end + end + + describe "when self is a proxy class" do + it "returns nil if no username was set for self's proxy connection" do + Net::HTTP.Proxy("localhost").proxy_user.should be_nil + end + + it "returns the username for self's proxy connection" do + Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks").proxy_user.should == "rspec" + end + end +end + +describe "Net::HTTP#proxy_user" do + describe "when self is no proxy class instance" do + it "returns nil" do + Net::HTTP.new("localhost", 3333).proxy_user.should be_nil + end + end + + describe "when self is a proxy class instance" do + it "returns nil if no username was set for self's proxy connection" do + Net::HTTP.Proxy("localhost").new("localhost", 3333).proxy_user.should be_nil + end + + it "returns the username for self's proxy connection" do + http_with_proxy = Net::HTTP.Proxy("localhost", 1234, "rspec", "rocks") + http_with_proxy.new("localhost", 3333).proxy_user.should == "rspec" + end + end +end diff --git a/spec/ruby/library/net-http/http/put2_spec.rb b/spec/ruby/library/net-http/http/put2_spec.rb new file mode 100644 index 0000000000..7b03a39d0b --- /dev/null +++ b/spec/ruby/library/net-http/http/put2_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_put' + +describe "Net::HTTP#put2" do + it_behaves_like :net_http_request_put, :put2 +end diff --git a/spec/ruby/library/net-http/http/put_spec.rb b/spec/ruby/library/net-http/http/put_spec.rb new file mode 100644 index 0000000000..75f3c243d4 --- /dev/null +++ b/spec/ruby/library/net-http/http/put_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#put" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends an put request to the passed path and returns the response" do + response = @http.put("/request", "test=test") + response.body.should == "Request type: PUT" + end + + it "returns a Net::HTTPResponse" do + @http.put("/request", "test=test").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/read_timeout_spec.rb b/spec/ruby/library/net-http/http/read_timeout_spec.rb new file mode 100644 index 0000000000..7a0d2f1d72 --- /dev/null +++ b/spec/ruby/library/net-http/http/read_timeout_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#read_timeout" do + it "returns the seconds to wait until reading one block" do + net = Net::HTTP.new("localhost") + net.read_timeout.should eql(60) + net.read_timeout = 10 + net.read_timeout.should eql(10) + end +end + +describe "Net::HTTP#read_timeout=" do + it "sets the seconds to wait till the connection is open" do + net = Net::HTTP.new("localhost") + net.read_timeout = 10 + net.read_timeout.should eql(10) + end + + it "returns the newly set value" do + net = Net::HTTP.new("localhost") + (net.read_timeout = 10).should eql(10) + end +end diff --git a/spec/ruby/library/net-http/http/request_get_spec.rb b/spec/ruby/library/net-http/http/request_get_spec.rb new file mode 100644 index 0000000000..98025a14a1 --- /dev/null +++ b/spec/ruby/library/net-http/http/request_get_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_get' + +describe "Net::HTTP#request_get" do + it_behaves_like :net_http_request_get, :get2 +end diff --git a/spec/ruby/library/net-http/http/request_head_spec.rb b/spec/ruby/library/net-http/http/request_head_spec.rb new file mode 100644 index 0000000000..8f514d4eee --- /dev/null +++ b/spec/ruby/library/net-http/http/request_head_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_head' + +describe "Net::HTTP#request_head" do + it_behaves_like :net_http_request_head, :request_head +end diff --git a/spec/ruby/library/net-http/http/request_post_spec.rb b/spec/ruby/library/net-http/http/request_post_spec.rb new file mode 100644 index 0000000000..719bd5a7ee --- /dev/null +++ b/spec/ruby/library/net-http/http/request_post_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_post' + +describe "Net::HTTP#request_post" do + it_behaves_like :net_http_request_post, :request_post +end diff --git a/spec/ruby/library/net-http/http/request_put_spec.rb b/spec/ruby/library/net-http/http/request_put_spec.rb new file mode 100644 index 0000000000..9fcf3a98d6 --- /dev/null +++ b/spec/ruby/library/net-http/http/request_put_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/request_put' + +describe "Net::HTTP#request_put" do + it_behaves_like :net_http_request_put, :request_put +end diff --git a/spec/ruby/library/net-http/http/request_spec.rb b/spec/ruby/library/net-http/http/request_spec.rb new file mode 100644 index 0000000000..356e605b3b --- /dev/null +++ b/spec/ruby/library/net-http/http/request_spec.rb @@ -0,0 +1,109 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#request" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + describe "when passed request_object" do + it "makes a HTTP Request based on the passed request_object" do + response = @http.request(Net::HTTP::Get.new("/request"), "test=test") + response.body.should == "Request type: GET" + + response = @http.request(Net::HTTP::Head.new("/request"), "test=test") + response.body.should be_nil + + response = @http.request(Net::HTTP::Post.new("/request"), "test=test") + response.body.should == "Request type: POST" + + response = @http.request(Net::HTTP::Put.new("/request"), "test=test") + response.body.should == "Request type: PUT" + + response = @http.request(Net::HTTP::Proppatch.new("/request"), "test=test") + response.body.should == "Request type: PROPPATCH" + + response = @http.request(Net::HTTP::Lock.new("/request"), "test=test") + response.body.should == "Request type: LOCK" + + response = @http.request(Net::HTTP::Unlock.new("/request"), "test=test") + response.body.should == "Request type: UNLOCK" + + # TODO: Does not work? + #response = @http.request(Net::HTTP::Options.new("/request"), "test=test") + #response.body.should be_nil + + response = @http.request(Net::HTTP::Propfind.new("/request"), "test=test") + response.body.should == "Request type: PROPFIND" + + response = @http.request(Net::HTTP::Delete.new("/request"), "test=test") + response.body.should == "Request type: DELETE" + + response = @http.request(Net::HTTP::Move.new("/request"), "test=test") + response.body.should == "Request type: MOVE" + + response = @http.request(Net::HTTP::Copy.new("/request"), "test=test") + response.body.should == "Request type: COPY" + + response = @http.request(Net::HTTP::Mkcol.new("/request"), "test=test") + response.body.should == "Request type: MKCOL" + + response = @http.request(Net::HTTP::Trace.new("/request"), "test=test") + response.body.should == "Request type: TRACE" + end + end + + describe "when passed request_object and request_body" do + it "sends the passed request_body when making the HTTP Request" do + response = @http.request(Net::HTTP::Get.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Head.new("/request/body"), "test=test") + response.body.should be_nil + + response = @http.request(Net::HTTP::Post.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Put.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Proppatch.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Lock.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Unlock.new("/request/body"), "test=test") + response.body.should == "test=test" + + # TODO: Does not work? + #response = @http.request(Net::HTTP::Options.new("/request/body"), "test=test") + #response.body.should be_nil + + response = @http.request(Net::HTTP::Propfind.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Delete.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Move.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Copy.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Mkcol.new("/request/body"), "test=test") + response.body.should == "test=test" + + response = @http.request(Net::HTTP::Trace.new("/request/body"), "test=test") + response.body.should == "test=test" + end + end +end diff --git a/spec/ruby/library/net-http/http/request_types_spec.rb b/spec/ruby/library/net-http/http/request_types_spec.rb new file mode 100644 index 0000000000..53aef1ee58 --- /dev/null +++ b/spec/ruby/library/net-http/http/request_types_spec.rb @@ -0,0 +1,254 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP::Get" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Get.should < Net::HTTPRequest + end + + it "represents the 'GET'-Request-Method" do + Net::HTTP::Get::METHOD.should == "GET" + end + + it "has no Request Body" do + Net::HTTP::Get::REQUEST_HAS_BODY.should be_false + end + + it "has a Response Body" do + Net::HTTP::Get::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Head" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Head.should < Net::HTTPRequest + end + + it "represents the 'HEAD'-Request-Method" do + Net::HTTP::Head::METHOD.should == "HEAD" + end + + it "has no Request Body" do + Net::HTTP::Head::REQUEST_HAS_BODY.should be_false + end + + it "has no Response Body" do + Net::HTTP::Head::RESPONSE_HAS_BODY.should be_false + end +end + +describe "Net::HTTP::Post" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Post.should < Net::HTTPRequest + end + + it "represents the 'POST'-Request-Method" do + Net::HTTP::Post::METHOD.should == "POST" + end + + it "has a Request Body" do + Net::HTTP::Post::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Post::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Put" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Put.should < Net::HTTPRequest + end + + it "represents the 'PUT'-Request-Method" do + Net::HTTP::Put::METHOD.should == "PUT" + end + + it "has a Request Body" do + Net::HTTP::Put::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Put::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Delete" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Delete.should < Net::HTTPRequest + end + + it "represents the 'DELETE'-Request-Method" do + Net::HTTP::Delete::METHOD.should == "DELETE" + end + + it "has no Request Body" do + Net::HTTP::Delete::REQUEST_HAS_BODY.should be_false + end + + it "has a Response Body" do + Net::HTTP::Delete::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Options" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Options.should < Net::HTTPRequest + end + + it "represents the 'OPTIONS'-Request-Method" do + Net::HTTP::Options::METHOD.should == "OPTIONS" + end + + it "has no Request Body" do + Net::HTTP::Options::REQUEST_HAS_BODY.should be_false + end + + it "has no Response Body" do + Net::HTTP::Options::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Trace" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Trace.should < Net::HTTPRequest + end + + it "represents the 'TRACE'-Request-Method" do + Net::HTTP::Trace::METHOD.should == "TRACE" + end + + it "has no Request Body" do + Net::HTTP::Trace::REQUEST_HAS_BODY.should be_false + end + + it "has a Response Body" do + Net::HTTP::Trace::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Propfind" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Propfind.should < Net::HTTPRequest + end + + it "represents the 'PROPFIND'-Request-Method" do + Net::HTTP::Propfind::METHOD.should == "PROPFIND" + end + + it "has a Request Body" do + Net::HTTP::Propfind::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Propfind::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Proppatch" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Proppatch.should < Net::HTTPRequest + end + + it "represents the 'PROPPATCH'-Request-Method" do + Net::HTTP::Proppatch::METHOD.should == "PROPPATCH" + end + + it "has a Request Body" do + Net::HTTP::Proppatch::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Proppatch::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Mkcol" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Mkcol.should < Net::HTTPRequest + end + + it "represents the 'MKCOL'-Request-Method" do + Net::HTTP::Mkcol::METHOD.should == "MKCOL" + end + + it "has a Request Body" do + Net::HTTP::Mkcol::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Mkcol::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Copy" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Copy.should < Net::HTTPRequest + end + + it "represents the 'COPY'-Request-Method" do + Net::HTTP::Copy::METHOD.should == "COPY" + end + + it "has no Request Body" do + Net::HTTP::Copy::REQUEST_HAS_BODY.should be_false + end + + it "has a Response Body" do + Net::HTTP::Copy::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Move" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Move.should < Net::HTTPRequest + end + + it "represents the 'MOVE'-Request-Method" do + Net::HTTP::Move::METHOD.should == "MOVE" + end + + it "has no Request Body" do + Net::HTTP::Move::REQUEST_HAS_BODY.should be_false + end + + it "has a Response Body" do + Net::HTTP::Move::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Lock" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Lock.should < Net::HTTPRequest + end + + it "represents the 'LOCK'-Request-Method" do + Net::HTTP::Lock::METHOD.should == "LOCK" + end + + it "has a Request Body" do + Net::HTTP::Lock::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Lock::RESPONSE_HAS_BODY.should be_true + end +end + +describe "Net::HTTP::Unlock" do + it "is a subclass of Net::HTTPRequest" do + Net::HTTP::Unlock.should < Net::HTTPRequest + end + + it "represents the 'UNLOCK'-Request-Method" do + Net::HTTP::Unlock::METHOD.should == "UNLOCK" + end + + it "has a Request Body" do + Net::HTTP::Unlock::REQUEST_HAS_BODY.should be_true + end + + it "has a Response Body" do + Net::HTTP::Unlock::RESPONSE_HAS_BODY.should be_true + end +end diff --git a/spec/ruby/library/net-http/http/send_request_spec.rb b/spec/ruby/library/net-http/http/send_request_spec.rb new file mode 100644 index 0000000000..af35c068ce --- /dev/null +++ b/spec/ruby/library/net-http/http/send_request_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#send_request" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + + # HEAD is special so handled separately + @methods = %w[ + GET POST PUT DELETE + OPTIONS + PROPFIND PROPPATCH LOCK UNLOCK + ] + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + # TODO: Does only work with GET and POST requests + describe "when passed type, path" do + it "sends a HTTP Request of the passed type to the passed path" do + response = @http.send_request("HEAD", "/request") + response.body.should be_nil + + (@methods - %w[POST PUT]).each do |method| + response = @http.send_request(method, "/request") + response.body.should == "Request type: #{method}" + end + end + end + + describe "when passed type, path, body" do + it "sends a HTTP Request with the passed body" do + response = @http.send_request("HEAD", "/request/body", "test=test") + response.body.should be_nil + + @methods.each do |method| + response = @http.send_request(method, "/request/body", "test=test") + response.body.should == "test=test" + end + end + end + + describe "when passed type, path, body, headers" do + it "sends a HTTP Request with the passed headers" do + referer = 'https://www.ruby-lang.org/'.freeze + + response = @http.send_request("HEAD", "/request/header", "test=test", "referer" => referer) + response.body.should be_nil + + @methods.each do |method| + response = @http.send_request(method, "/request/header", "test=test", "referer" => referer) + response.body.should include({ "Referer" => referer }.inspect.delete("{}")) + end + end + end +end diff --git a/spec/ruby/library/net-http/http/set_debug_output_spec.rb b/spec/ruby/library/net-http/http/set_debug_output_spec.rb new file mode 100644 index 0000000000..5ceecb39fb --- /dev/null +++ b/spec/ruby/library/net-http/http/set_debug_output_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' +require 'net/http' +require "stringio" +require_relative 'fixtures/http_server' + +describe "Net::HTTP#set_debug_output when passed io" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.new("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sets the passed io as output stream for debugging" do + io = StringIO.new + + @http.set_debug_output(io) + @http.start + io.string.should_not be_empty + size = io.string.size + + @http.get("/") + io.string.size.should > size + end + + it "outputs a warning when the connection has already been started" do + @http.start + -> { @http.set_debug_output(StringIO.new) }.should complain(/Net::HTTP#set_debug_output called after HTTP started/) + end +end diff --git a/spec/ruby/library/net-http/http/shared/request_get.rb b/spec/ruby/library/net-http/http/shared/request_get.rb new file mode 100644 index 0000000000..d25f32049b --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/request_get.rb @@ -0,0 +1,41 @@ +describe :net_http_request_get, shared: true do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + describe "when passed no block" do + it "sends a GET request to the passed path and returns the response" do + response = @http.send(@method, "/request") + response.body.should == "Request type: GET" + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request") + response.should be_kind_of(Net::HTTPResponse) + end + end + + describe "when passed a block" do + it "sends a GET request to the passed path and returns the response" do + response = @http.send(@method, "/request") {} + response.body.should == "Request type: GET" + end + + it "yields the response to the passed block" do + @http.send(@method, "/request") do |response| + response.body.should == "Request type: GET" + end + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request") {} + response.should be_kind_of(Net::HTTPResponse) + end + end +end diff --git a/spec/ruby/library/net-http/http/shared/request_head.rb b/spec/ruby/library/net-http/http/shared/request_head.rb new file mode 100644 index 0000000000..78b555884b --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/request_head.rb @@ -0,0 +1,41 @@ +describe :net_http_request_head, shared: true do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + describe "when passed no block" do + it "sends a head request to the passed path and returns the response" do + response = @http.send(@method, "/request") + response.body.should be_nil + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request") + response.should be_kind_of(Net::HTTPResponse) + end + end + + describe "when passed a block" do + it "sends a head request to the passed path and returns the response" do + response = @http.send(@method, "/request") {} + response.body.should be_nil + end + + it "yields the response to the passed block" do + @http.send(@method, "/request") do |response| + response.body.should be_nil + end + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request") {} + response.should be_kind_of(Net::HTTPResponse) + end + end +end diff --git a/spec/ruby/library/net-http/http/shared/request_post.rb b/spec/ruby/library/net-http/http/shared/request_post.rb new file mode 100644 index 0000000000..e832411c48 --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/request_post.rb @@ -0,0 +1,41 @@ +describe :net_http_request_post, shared: true do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + describe "when passed no block" do + it "sends a post request to the passed path and returns the response" do + response = @http.send(@method, "/request", "test=test") + response.body.should == "Request type: POST" + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request", "test=test") + response.should be_kind_of(Net::HTTPResponse) + end + end + + describe "when passed a block" do + it "sends a post request to the passed path and returns the response" do + response = @http.send(@method, "/request", "test=test") {} + response.body.should == "Request type: POST" + end + + it "yields the response to the passed block" do + @http.send(@method, "/request", "test=test") do |response| + response.body.should == "Request type: POST" + end + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request", "test=test") {} + response.should be_kind_of(Net::HTTPResponse) + end + end +end diff --git a/spec/ruby/library/net-http/http/shared/request_put.rb b/spec/ruby/library/net-http/http/shared/request_put.rb new file mode 100644 index 0000000000..3b902f4957 --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/request_put.rb @@ -0,0 +1,41 @@ +describe :net_http_request_put, shared: true do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + describe "when passed no block" do + it "sends a put request to the passed path and returns the response" do + response = @http.send(@method, "/request", "test=test") + response.body.should == "Request type: PUT" + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request", "test=test") + response.should be_kind_of(Net::HTTPResponse) + end + end + + describe "when passed a block" do + it "sends a put request to the passed path and returns the response" do + response = @http.send(@method, "/request", "test=test") {} + response.body.should == "Request type: PUT" + end + + it "yields the response to the passed block" do + @http.send(@method, "/request", "test=test") do |response| + response.body.should == "Request type: PUT" + end + end + + it "returns a Net::HTTPResponse object" do + response = @http.send(@method, "/request", "test=test") {} + response.should be_kind_of(Net::HTTPResponse) + end + end +end diff --git a/spec/ruby/library/net-http/http/shared/started.rb b/spec/ruby/library/net-http/http/shared/started.rb new file mode 100644 index 0000000000..9ff6272c31 --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/started.rb @@ -0,0 +1,26 @@ +describe :net_http_started_p, shared: true do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.new("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "returns true when self has been started" do + @http.start + @http.send(@method).should be_true + end + + it "returns false when self has not been started yet" do + @http.send(@method).should be_false + end + + it "returns false when self has been stopped again" do + @http.start + @http.finish + @http.send(@method).should be_false + end +end diff --git a/spec/ruby/library/net-http/http/shared/version_1_1.rb b/spec/ruby/library/net-http/http/shared/version_1_1.rb new file mode 100644 index 0000000000..db3d6a986d --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/version_1_1.rb @@ -0,0 +1,6 @@ +describe :net_http_version_1_1_p, shared: true do + it "returns the state of net/http 1.1 features" do + Net::HTTP.version_1_2 + Net::HTTP.send(@method).should be_false + end +end diff --git a/spec/ruby/library/net-http/http/shared/version_1_2.rb b/spec/ruby/library/net-http/http/shared/version_1_2.rb new file mode 100644 index 0000000000..b044182c60 --- /dev/null +++ b/spec/ruby/library/net-http/http/shared/version_1_2.rb @@ -0,0 +1,6 @@ +describe :net_http_version_1_2_p, shared: true do + it "returns the state of net/http 1.2 features" do + Net::HTTP.version_1_2 + Net::HTTP.send(@method).should be_true + end +end diff --git a/spec/ruby/library/net-http/http/socket_type_spec.rb b/spec/ruby/library/net-http/http/socket_type_spec.rb new file mode 100644 index 0000000000..f6826777b0 --- /dev/null +++ b/spec/ruby/library/net-http/http/socket_type_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP.socket_type" do + it "returns BufferedIO" do + Net::HTTP.socket_type.should == Net::BufferedIO + end +end diff --git a/spec/ruby/library/net-http/http/start_spec.rb b/spec/ruby/library/net-http/http/start_spec.rb new file mode 100644 index 0000000000..0ce3e79269 --- /dev/null +++ b/spec/ruby/library/net-http/http/start_spec.rb @@ -0,0 +1,111 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP.start" do + before :each do + NetHTTPSpecs.start_server + @port = NetHTTPSpecs.port + end + + after :each do + NetHTTPSpecs.stop_server + end + + describe "when not passed a block" do + before :each do + @http = Net::HTTP.start("localhost", @port) + end + + after :each do + @http.finish if @http.started? + end + + it "returns a new Net::HTTP object for the passed address and port" do + @http.should be_kind_of(Net::HTTP) + @http.address.should == "localhost" + @http.port.should == @port + end + + it "opens the tcp connection" do + @http.started?.should be_true + end + end + + describe "when passed a block" do + it "returns the blocks return value" do + Net::HTTP.start("localhost", @port) { :test }.should == :test + end + + it "yields the new Net::HTTP object to the block" do + yielded = false + Net::HTTP.start("localhost", @port) do |net| + yielded = true + net.should be_kind_of(Net::HTTP) + end + yielded.should be_true + end + + it "opens the tcp connection before yielding" do + Net::HTTP.start("localhost", @port) { |http| http.started?.should be_true } + end + + it "closes the tcp connection after yielding" do + net = nil + Net::HTTP.start("localhost", @port) { |x| net = x } + net.started?.should be_false + end + end +end + +describe "Net::HTTP#start" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.new("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "returns self" do + @http.start.should equal(@http) + end + + it "opens the tcp connection" do + @http.start + @http.started?.should be_true + end + + describe "when self has already been started" do + it "raises an IOError" do + @http.start + -> { @http.start }.should raise_error(IOError) + end + end + + describe "when passed a block" do + it "returns the blocks return value" do + @http.start { :test }.should == :test + end + + it "yields the new Net::HTTP object to the block" do + yielded = false + @http.start do |http| + yielded = true + http.should equal(@http) + end + yielded.should be_true + end + + it "opens the tcp connection before yielding" do + @http.start { |http| http.started?.should be_true } + end + + it "closes the tcp connection after yielding" do + @http.start { } + @http.started?.should be_false + end + end +end diff --git a/spec/ruby/library/net-http/http/started_spec.rb b/spec/ruby/library/net-http/http/started_spec.rb new file mode 100644 index 0000000000..cbb82ceefa --- /dev/null +++ b/spec/ruby/library/net-http/http/started_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' +require_relative 'shared/started' + +describe "Net::HTTP#started?" do + it_behaves_like :net_http_started_p, :started? +end diff --git a/spec/ruby/library/net-http/http/trace_spec.rb b/spec/ruby/library/net-http/http/trace_spec.rb new file mode 100644 index 0000000000..9809d537c5 --- /dev/null +++ b/spec/ruby/library/net-http/http/trace_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#trace" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends a TRACE request to the passed path and returns the response" do + response = @http.trace("/request") + response.body.should == "Request type: TRACE" + end + + it "returns a Net::HTTPResponse" do + @http.trace("/request").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/unlock_spec.rb b/spec/ruby/library/net-http/http/unlock_spec.rb new file mode 100644 index 0000000000..adf0b49f65 --- /dev/null +++ b/spec/ruby/library/net-http/http/unlock_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/http_server' + +describe "Net::HTTP#unlock" do + before :each do + NetHTTPSpecs.start_server + @http = Net::HTTP.start("localhost", NetHTTPSpecs.port) + end + + after :each do + @http.finish if @http.started? + NetHTTPSpecs.stop_server + end + + it "sends an UNLOCK request to the passed path and returns the response" do + response = @http.unlock("/request", "test=test") + response.body.should == "Request type: UNLOCK" + end + + it "returns a Net::HTTPResponse" do + @http.unlock("/request", "test=test").should be_kind_of(Net::HTTPResponse) + end +end diff --git a/spec/ruby/library/net-http/http/use_ssl_spec.rb b/spec/ruby/library/net-http/http/use_ssl_spec.rb new file mode 100644 index 0000000000..912a62a8ba --- /dev/null +++ b/spec/ruby/library/net-http/http/use_ssl_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTP#use_ssl?" do + it "returns false" do + http = Net::HTTP.new("localhost") + http.use_ssl?.should be_false + end +end diff --git a/spec/ruby/library/net-http/http/version_1_1_spec.rb b/spec/ruby/library/net-http/http/version_1_1_spec.rb new file mode 100644 index 0000000000..34a4ac8a6b --- /dev/null +++ b/spec/ruby/library/net-http/http/version_1_1_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'shared/version_1_1' + +describe "Net::HTTP.version_1_1?" do + it_behaves_like :net_http_version_1_1_p, :version_1_1? +end diff --git a/spec/ruby/library/net-http/http/version_1_2_spec.rb b/spec/ruby/library/net-http/http/version_1_2_spec.rb new file mode 100644 index 0000000000..e994511aea --- /dev/null +++ b/spec/ruby/library/net-http/http/version_1_2_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'shared/version_1_2' + +describe "Net::HTTP.version_1_2" do + it "turns on net/http 1.2 features" do + Net::HTTP.version_1_2 + + Net::HTTP.version_1_2?.should be_true + Net::HTTP.version_1_1?.should be_false + end + + it "returns true" do + Net::HTTP.version_1_2.should be_true + end +end + +describe "Net::HTTP.version_1_2?" do + it_behaves_like :net_http_version_1_2_p, :version_1_2? +end diff --git a/spec/ruby/library/net-http/httpexceptions/fixtures/classes.rb b/spec/ruby/library/net-http/httpexceptions/fixtures/classes.rb new file mode 100644 index 0000000000..abe8855eff --- /dev/null +++ b/spec/ruby/library/net-http/httpexceptions/fixtures/classes.rb @@ -0,0 +1,5 @@ +module NetHTTPExceptionsSpecs + class Simple < StandardError + include Net::HTTPExceptions + end +end diff --git a/spec/ruby/library/net-http/httpexceptions/initialize_spec.rb b/spec/ruby/library/net-http/httpexceptions/initialize_spec.rb new file mode 100644 index 0000000000..5316cca69d --- /dev/null +++ b/spec/ruby/library/net-http/httpexceptions/initialize_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPExceptions#initialize when passed message, response" do + before :each do + @exception = NetHTTPExceptionsSpecs::Simple.new("error message", "a http response") + end + + it "calls super with the passed message" do + @exception.message.should == "error message" + end + + it "sets self's response to the passed response" do + @exception.response.should == "a http response" + end +end diff --git a/spec/ruby/library/net-http/httpexceptions/response_spec.rb b/spec/ruby/library/net-http/httpexceptions/response_spec.rb new file mode 100644 index 0000000000..d718b1ae21 --- /dev/null +++ b/spec/ruby/library/net-http/httpexceptions/response_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPExceptions#response" do + it "returns self's response" do + exception = NetHTTPExceptionsSpecs::Simple.new("error message", "a http response") + exception.response.should == "a http response" + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/body_exist_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/body_exist_spec.rb new file mode 100644 index 0000000000..6c886499ca --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/body_exist_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#body_exist?" do + it "returns true when the response is expected to have a body" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.body_exist?.should be_true + + request = Net::HTTPGenericRequest.new("POST", true, false, "/some/path") + request.body_exist?.should be_false + end + + describe "when $VERBOSE is true" do + it "emits a warning" do + request = Net::HTTPGenericRequest.new("POST", true, false, "/some/path") + -> { + request.body_exist? + }.should complain(/body_exist\? is obsolete/, verbose: true) + end + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/body_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/body_spec.rb new file mode 100644 index 0000000000..5f7315f303 --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/body_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' +require 'net/http' +require "stringio" + +describe "Net::HTTPGenericRequest#body" do + it "returns self's request body" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.body.should be_nil + + request.body = "Some Content" + request.body.should == "Some Content" + end +end + +describe "Net::HTTPGenericRequest#body=" do + before :each do + @request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + end + + it "sets self's body content to the passed String" do + @request.body = "Some Content" + @request.body.should == "Some Content" + end + + it "sets self's body stream to nil" do + @request.body_stream = StringIO.new("") + @request.body = "Some Content" + @request.body_stream.should be_nil + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/body_stream_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/body_stream_spec.rb new file mode 100644 index 0000000000..dea1c8c883 --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/body_stream_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require 'net/http' +require "stringio" + +describe "Net::HTTPGenericRequest#body_stream" do + it "returns self's body stream Object" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.body_stream.should be_nil + + stream = StringIO.new("test") + request.body_stream = stream + request.body_stream.should equal(stream) + end +end + +describe "Net::HTTPGenericRequest#body_stream=" do + before :each do + @request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + @stream = StringIO.new("test") + end + + it "sets self's body stream to the passed Object" do + @request.body_stream = @stream + @request.body_stream.should equal(@stream) + end + + it "sets self's body to nil" do + @request.body = "Some Content" + @request.body_stream = @stream + @request.body.should be_nil + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb new file mode 100644 index 0000000000..a09f9d5bec --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb @@ -0,0 +1,135 @@ +require_relative '../../../spec_helper' +require 'net/http' +require "stringio" + +describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do + before :each do + @socket = StringIO.new(+"") + @buffered_socket = Net::BufferedIO.new(@socket) + end + + it "executes the request over the socket to the path using the HTTP version" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + + request.exec(@buffered_socket, "1.1", "/some/path") + str = @socket.string + + str.should =~ %r[POST /some/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str[-4..-1].should == "\r\n\r\n" + + request = Net::HTTPGenericRequest.new("GET", true, true, "/some/path", + "Content-Type" => "text/html") + + request.exec(@buffered_socket, "1.0", "/some/other/path") + str = @socket.string + + str.should =~ %r[GET /some/other/path HTTP/1.0\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: text/html\r\n] + str[-4..-1].should == "\r\n\r\n" + end + + describe "when a request body is set" do + ruby_version_is ""..."4.0" do + it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.body = "Some Content" + + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string + + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n] + str.should =~ %r[Content-Length: 12\r\n] + str[-16..-1].should == "\r\n\r\nSome Content" + end + end + + it "correctly sets the 'Content-Length' header and includes the body" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", + "Content-Type" => "text/html") + request.body = "Some Content" + + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string + + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: text/html\r\n] + str.should =~ %r[Content-Length: 12\r\n] + str[-16..-1].should == "\r\n\r\nSome Content" + end + end + + describe "when a body stream is set" do + ruby_version_is ""..."4.0" do + it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", + "Content-Length" => "10") + request.body_stream = StringIO.new("a" * 20) + + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string + + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n] + str.should =~ %r[Content-Length: 10\r\n] + str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa" + end + end + + it "sends the whole stream, regardless of the 'Content-Length' header" do + request = Net::HTTPGenericRequest.new("POST", true, true,"/some/path", + "Content-Type" => "text/html", + "Content-Length" => "10") + request.body_stream = StringIO.new("a" * 20) + + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string + + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: text/html\r\n] + str.should =~ %r[Content-Length: 10\r\n] + str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa" + end + + it "sends the request in chunks when 'Transfer-Encoding' is set to 'chunked'" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", + "Content-Type" => "text/html", + "Transfer-Encoding" => "chunked") + datasize = 1024 * 10 + request.body_stream = StringIO.new("a" * datasize) + + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string + + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: text/html\r\n] + str.should =~ %r[Transfer-Encoding: chunked\r\n] + str =~ %r[\r\n\r\n] + str = $' + while datasize > 0 + chunk_size_line, str = str.split(/\r\n/, 2) + chunk_size = chunk_size_line[/\A[0-9A-Fa-f]+/].to_i(16) + str.slice!(0, chunk_size).should == 'a' * chunk_size + datasize -= chunk_size + str.slice!(0, 2).should == "\r\n" + end + datasize.should == 0 + str.should == %"0\r\n\r\n" + end + + it "raises an ArgumentError when the 'Content-Length' is not set or 'Transfer-Encoding' is not set to 'chunked'" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", + "Content-Type" => "text/html") + request.body_stream = StringIO.new("Some Content") + + -> { request.exec(@buffered_socket, "1.1", "/some/other/path") }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/inspect_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/inspect_spec.rb new file mode 100644 index 0000000000..d03b6e6953 --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/inspect_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#inspect" do + it "returns a String representation of self" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.inspect.should == "#<Net::HTTPGenericRequest POST>" + + request = Net::HTTPGenericRequest.new("GET", false, true, "/some/path") + request.inspect.should == "#<Net::HTTPGenericRequest GET>" + + request = Net::HTTPGenericRequest.new("BLA", true, true, "/some/path") + request.inspect.should == "#<Net::HTTPGenericRequest BLA>" + + # Subclasses + request = Net::HTTP::Get.new("/some/path") + request.inspect.should == "#<Net::HTTP::Get GET>" + + request = Net::HTTP::Post.new("/some/path") + request.inspect.should == "#<Net::HTTP::Post POST>" + + request = Net::HTTP::Trace.new("/some/path") + request.inspect.should == "#<Net::HTTP::Trace TRACE>" + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/method_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/method_spec.rb new file mode 100644 index 0000000000..794bd328cd --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/method_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#method" do + it "returns self's request method" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.method.should == "POST" + + request = Net::HTTPGenericRequest.new("GET", false, true, "/some/path") + request.method.should == "GET" + + request = Net::HTTPGenericRequest.new("BLA", true, true, "/some/path") + request.method.should == "BLA" + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/path_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/path_spec.rb new file mode 100644 index 0000000000..a9fac3f67e --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/path_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#path" do + it "returns self's request path" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.path.should == "/some/path" + + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/other/path") + request.path.should == "/some/other/path" + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/request_body_permitted_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/request_body_permitted_spec.rb new file mode 100644 index 0000000000..1713b59baf --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/request_body_permitted_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#request_body_permitted?" do + it "returns true when the request is expected to have a body" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.request_body_permitted?.should be_true + + request = Net::HTTPGenericRequest.new("POST", false, true, "/some/path") + request.request_body_permitted?.should be_false + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/response_body_permitted_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/response_body_permitted_spec.rb new file mode 100644 index 0000000000..2f0751c344 --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/response_body_permitted_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#response_body_permitted?" do + it "returns true when the response is expected to have a body" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.response_body_permitted?.should be_true + + request = Net::HTTPGenericRequest.new("POST", true, false, "/some/path") + request.response_body_permitted?.should be_false + end +end diff --git a/spec/ruby/library/net-http/httpgenericrequest/set_body_internal_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/set_body_internal_spec.rb new file mode 100644 index 0000000000..358aa6cde3 --- /dev/null +++ b/spec/ruby/library/net-http/httpgenericrequest/set_body_internal_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPGenericRequest#set_body_internal when passed string" do + before :each do + @request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + end + + it "sets self's body to the passed string" do + @request.set_body_internal("Some Content") + @request.body.should == "Some Content" + end + + it "raises an ArgumentError when the body or body_stream of self have already been set" do + @request.body = "Some Content" + -> { @request.set_body_internal("Some other Content") }.should raise_error(ArgumentError) + + @request.body_stream = "Some Content" + -> { @request.set_body_internal("Some other Content") }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/net-http/httpheader/add_field_spec.rb b/spec/ruby/library/net-http/httpheader/add_field_spec.rb new file mode 100644 index 0000000000..8cd3d33517 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/add_field_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#add_field when passed key, value" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "adds the passed value to the header entry with the passed key" do + @headers.add_field("My-Header", "a") + @headers.get_fields("My-Header").should == ["a"] + + @headers.add_field("My-Header", "b") + @headers.get_fields("My-Header").should == ["a", "b"] + + @headers.add_field("My-Header", "c") + @headers.get_fields("My-Header").should == ["a", "b", "c"] + end + + it "is case-insensitive" do + @headers.add_field("My-Header", "a") + @headers.get_fields("My-Header").should == ["a"] + + @headers.add_field("my-header", "b") + @headers.get_fields("My-Header").should == ["a", "b"] + + @headers.add_field("MY-HEADER", "c") + @headers.get_fields("My-Header").should == ["a", "b", "c"] + end +end diff --git a/spec/ruby/library/net-http/httpheader/basic_auth_spec.rb b/spec/ruby/library/net-http/httpheader/basic_auth_spec.rb new file mode 100644 index 0000000000..db7ca84d13 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/basic_auth_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#basic_auth when passed account, password" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "sets the 'Authorization' Header entry for basic authorization" do + @headers.basic_auth("rubyspec", "rocks") + @headers["Authorization"].should == "Basic cnVieXNwZWM6cm9ja3M=" + end +end diff --git a/spec/ruby/library/net-http/httpheader/canonical_each_spec.rb b/spec/ruby/library/net-http/httpheader/canonical_each_spec.rb new file mode 100644 index 0000000000..64a5cae89e --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/canonical_each_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/each_capitalized' + +describe "Net::HTTPHeader#canonical_each" do + it_behaves_like :net_httpheader_each_capitalized, :canonical_each +end diff --git a/spec/ruby/library/net-http/httpheader/chunked_spec.rb b/spec/ruby/library/net-http/httpheader/chunked_spec.rb new file mode 100644 index 0000000000..b32a0aab38 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/chunked_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#chunked?" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns true if the 'Transfer-Encoding' header entry is set to chunked" do + @headers.chunked?.should be_false + + @headers["Transfer-Encoding"] = "bla" + @headers.chunked?.should be_false + + @headers["Transfer-Encoding"] = "blachunkedbla" + @headers.chunked?.should be_false + + @headers["Transfer-Encoding"] = "chunked" + @headers.chunked?.should be_true + end +end diff --git a/spec/ruby/library/net-http/httpheader/content_length_spec.rb b/spec/ruby/library/net-http/httpheader/content_length_spec.rb new file mode 100644 index 0000000000..f05c5f8d8b --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/content_length_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#content_length" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns nil if no 'Content-Length' header entry is set" do + @headers.content_length.should be_nil + end + + it "raises a Net::HTTPHeaderSyntaxError when the 'Content-Length' header entry has an invalid format" do + @headers["Content-Length"] = "invalid" + -> { @headers.content_length }.should raise_error(Net::HTTPHeaderSyntaxError) + end + + it "returns the value of the 'Content-Length' header entry as an Integer" do + @headers["Content-Length"] = "123" + @headers.content_length.should eql(123) + + @headers["Content-Length"] = "123valid" + @headers.content_length.should eql(123) + + @headers["Content-Length"] = "valid123" + @headers.content_length.should eql(123) + end +end + +describe "Net::HTTPHeader#content_length=" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "removes the 'Content-Length' entry if passed false or nil" do + @headers["Content-Length"] = "123" + @headers.content_length = nil + @headers["Content-Length"].should be_nil + end + + it "sets the 'Content-Length' entry to the passed value" do + @headers.content_length = "123" + @headers["Content-Length"].should == "123" + + @headers.content_length = "123valid" + @headers["Content-Length"].should == "123" + end + + it "sets the 'Content-Length' entry to 0 if the passed value is not valid" do + @headers.content_length = "invalid123" + @headers["Content-Length"].should == "0" + end +end diff --git a/spec/ruby/library/net-http/httpheader/content_range_spec.rb b/spec/ruby/library/net-http/httpheader/content_range_spec.rb new file mode 100644 index 0000000000..09737141a5 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/content_range_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#content_range" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns a Range object that represents the 'Content-Range' header entry" do + @headers["Content-Range"] = "bytes 0-499/1234" + @headers.content_range.should == (0..499) + + @headers["Content-Range"] = "bytes 500-1233/1234" + @headers.content_range.should == (500..1233) + end + + it "returns nil when there is no 'Content-Range' header entry" do + @headers.content_range.should be_nil + end + + it "raises a Net::HTTPHeaderSyntaxError when the 'Content-Range' has an invalid format" do + @headers["Content-Range"] = "invalid" + -> { @headers.content_range }.should raise_error(Net::HTTPHeaderSyntaxError) + + @headers["Content-Range"] = "bytes 123-abc" + -> { @headers.content_range }.should raise_error(Net::HTTPHeaderSyntaxError) + + @headers["Content-Range"] = "bytes abc-123" + -> { @headers.content_range }.should raise_error(Net::HTTPHeaderSyntaxError) + end +end diff --git a/spec/ruby/library/net-http/httpheader/content_type_spec.rb b/spec/ruby/library/net-http/httpheader/content_type_spec.rb new file mode 100644 index 0000000000..a6e1ae1093 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/content_type_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/set_content_type' + +describe "Net::HTTPHeader#content_type" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns the content type string, as per 'Content-Type' header entry" do + @headers["Content-Type"] = "text/html" + @headers.content_type.should == "text/html" + + @headers["Content-Type"] = "text/html;charset=utf-8" + @headers.content_type.should == "text/html" + end + + it "returns nil if the 'Content-Type' header entry does not exist" do + @headers.content_type.should be_nil + end +end + +describe "Net::HTTPHeader#content_type=" do + it_behaves_like :net_httpheader_set_content_type, :content_type= +end diff --git a/spec/ruby/library/net-http/httpheader/delete_spec.rb b/spec/ruby/library/net-http/httpheader/delete_spec.rb new file mode 100644 index 0000000000..8d929dbd86 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/delete_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#delete when passed key" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "removes the header entry with the passed key" do + @headers["My-Header"] = "test" + @headers.delete("My-Header") + + @headers["My-Header"].should be_nil + @headers.size.should eql(0) + end + + it "returns the removed values" do + @headers["My-Header"] = "test" + @headers.delete("My-Header").should == ["test"] + end + + it "is case-insensitive" do + @headers["My-Header"] = "test" + @headers.delete("my-header") + + @headers["My-Header"].should be_nil + @headers.size.should eql(0) + end +end diff --git a/spec/ruby/library/net-http/httpheader/each_capitalized_name_spec.rb b/spec/ruby/library/net-http/httpheader/each_capitalized_name_spec.rb new file mode 100644 index 0000000000..27713577f9 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_capitalized_name_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#each_capitalized_name" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + @headers["My-Header"] = "test" + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + end + + describe "when passed a block" do + it "yields each header key to the passed block (keys capitalized)" do + res = [] + @headers.each_capitalized_name do |key| + res << key + end + res.sort.should == ["My-Header", "My-Other-Header"] + end + end + + describe "when passed no block" do + it "returns an Enumerator" do + enumerator = @headers.each_capitalized_name + enumerator.should be_an_instance_of(Enumerator) + + res = [] + enumerator.each do |key| + res << key + end + res.sort.should == ["My-Header", "My-Other-Header"] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/each_capitalized_spec.rb b/spec/ruby/library/net-http/httpheader/each_capitalized_spec.rb new file mode 100644 index 0000000000..1e853995ea --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_capitalized_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/each_capitalized' + +describe "Net::HTTPHeader#each_capitalized" do + it_behaves_like :net_httpheader_each_capitalized, :each_capitalized +end diff --git a/spec/ruby/library/net-http/httpheader/each_header_spec.rb b/spec/ruby/library/net-http/httpheader/each_header_spec.rb new file mode 100644 index 0000000000..869feebacf --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_header_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/each_header' + +describe "Net::HTTPHeader#each_header" do + it_behaves_like :net_httpheader_each_header, :each_header +end diff --git a/spec/ruby/library/net-http/httpheader/each_key_spec.rb b/spec/ruby/library/net-http/httpheader/each_key_spec.rb new file mode 100644 index 0000000000..1ad145629f --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_key_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/each_name' + +describe "Net::HTTPHeader#each_key" do + it_behaves_like :net_httpheader_each_name, :each_key +end diff --git a/spec/ruby/library/net-http/httpheader/each_name_spec.rb b/spec/ruby/library/net-http/httpheader/each_name_spec.rb new file mode 100644 index 0000000000..f819bd989d --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_name_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/each_name' + +describe "Net::HTTPHeader#each_name" do + it_behaves_like :net_httpheader_each_name, :each_name +end diff --git a/spec/ruby/library/net-http/httpheader/each_spec.rb b/spec/ruby/library/net-http/httpheader/each_spec.rb new file mode 100644 index 0000000000..ff37249d0a --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/each_header' + +describe "Net::HTTPHeader#each" do + it_behaves_like :net_httpheader_each_header, :each +end diff --git a/spec/ruby/library/net-http/httpheader/each_value_spec.rb b/spec/ruby/library/net-http/httpheader/each_value_spec.rb new file mode 100644 index 0000000000..b71df58c65 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/each_value_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#each_value" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + @headers["My-Header"] = "test" + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + end + + describe "when passed a block" do + it "yields each header entry's joined values" do + res = [] + @headers.each_value do |value| + res << value + end + res.sort.should == ["a, b", "test"] + end + end + + describe "when passed no block" do + it "returns an Enumerator" do + enumerator = @headers.each_value + enumerator.should be_an_instance_of(Enumerator) + + res = [] + enumerator.each do |key| + res << key + end + res.sort.should == ["a, b", "test"] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/element_reference_spec.rb b/spec/ruby/library/net-http/httpheader/element_reference_spec.rb new file mode 100644 index 0000000000..1003c41af9 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/element_reference_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#[] when passed key" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns the value of the header entry with the passed key" do + @headers["My-Header"] = "test" + @headers["My-Header"].should == "test" + @headers["My-Other-Header"] = "another test" + @headers["My-Other-Header"].should == "another test" + end + + it "is case-insensitive" do + @headers["My-Header"] = "test" + + @headers['My-Header'].should == "test" + @headers['my-Header'].should == "test" + @headers['My-header'].should == "test" + @headers['my-header'].should == "test" + @headers['MY-HEADER'].should == "test" + end + + it "returns multi-element values joined together" do + @headers["My-Header"] = "test" + @headers.add_field("My-Header", "another test") + @headers.add_field("My-Header", "and one more") + + @headers["My-Header"].should == "test, another test, and one more" + end + + it "returns nil for non-existing entries" do + @headers["My-Header"].should be_nil + @headers["My-Other-Header"].should be_nil + end +end diff --git a/spec/ruby/library/net-http/httpheader/element_set_spec.rb b/spec/ruby/library/net-http/httpheader/element_set_spec.rb new file mode 100644 index 0000000000..376df2f977 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/element_set_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#[]= when passed key, value" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "sets the header entry with the passed key to the passed value" do + @headers["My-Header"] = "test" + @headers["My-Header"].should == "test" + + @headers["My-Header"] = "overwritten" + @headers["My-Header"].should == "overwritten" + + @headers["My-Other-Header"] = "another test" + @headers["My-Other-Header"].should == "another test" + end + + it "is case-insensitive" do + @headers['My-Header'] = "test" + @headers['my-Header'] = "another test" + @headers['My-header'] = "and one more test" + @headers['my-header'] = "and another one" + @headers['MY-HEADER'] = "last one" + + @headers["My-Header"].should == "last one" + @headers.size.should eql(1) + end + + it "removes the header entry with the passed key when the value is false or nil" do + @headers['My-Header'] = "test" + @headers['My-Header'] = nil + @headers['My-Header'].should be_nil + + @headers['My-Header'] = "test" + @headers['My-Header'] = false + @headers['My-Header'].should be_nil + end +end diff --git a/spec/ruby/library/net-http/httpheader/fetch_spec.rb b/spec/ruby/library/net-http/httpheader/fetch_spec.rb new file mode 100644 index 0000000000..58c69c0377 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/fetch_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#fetch" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + describe "when passed key" do + it "returns the header entry for the passed key" do + @headers["My-Header"] = "test" + @headers.fetch("My-Header").should == "test" + + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + @headers.add_field("My-Other-Header", "c") + @headers.fetch("My-Other-Header").should == "a, b, c" + end + + it "is case-insensitive" do + @headers["My-Header"] = "test" + @headers.fetch("my-header").should == "test" + @headers.fetch("MY-HEADER").should == "test" + end + + it "returns nil when there is no entry for the passed key" do + -> { @headers.fetch("my-header") }.should raise_error(IndexError) + end + end + + describe "when passed key, default" do + it "returns the header entry for the passed key" do + @headers["My-Header"] = "test" + @headers.fetch("My-Header", "bla").should == "test" + + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + @headers.add_field("My-Other-Header", "c") + @headers.fetch("My-Other-Header", "bla").should == "a, b, c" + end + + # TODO: This raises a NoMethodError: undefined method `join' for "bla":String + it "returns the default value when there is no entry for the passed key" do + @headers.fetch("My-Header", "bla").should == "bla" + end + end + + describe "when passed key and block" do + it "returns the header entry for the passed key" do + @headers["My-Header"] = "test" + @headers.fetch("My-Header") {}.should == "test" + + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + @headers.add_field("My-Other-Header", "c") + -> { + @result = @headers.fetch("My-Other-Header", "bla") {} + }.should complain(/block supersedes default value argument/) + @result.should == "a, b, c" + end + + # TODO: This raises a NoMethodError: undefined method `join' for "redaeh-ym":String + it "yieldsand returns the block's return value when there is no entry for the passed key" do + @headers.fetch("My-Header") { |key| key.reverse }.should == "redaeh-ym" + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/fixtures/classes.rb b/spec/ruby/library/net-http/httpheader/fixtures/classes.rb new file mode 100644 index 0000000000..b5ec6abd75 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/fixtures/classes.rb @@ -0,0 +1,11 @@ +module NetHTTPHeaderSpecs + class Example + include Net::HTTPHeader + + attr_accessor :body + + def initialize + initialize_http_header({}) + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/form_data_spec.rb b/spec/ruby/library/net-http/httpheader/form_data_spec.rb new file mode 100644 index 0000000000..acd913f53a --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/form_data_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/set_form_data' + +describe "Net::HTTPHeader#form_data=" do + it_behaves_like :net_httpheader_set_form_data, :form_data= +end diff --git a/spec/ruby/library/net-http/httpheader/get_fields_spec.rb b/spec/ruby/library/net-http/httpheader/get_fields_spec.rb new file mode 100644 index 0000000000..0278bcede2 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/get_fields_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#get_fields when passed key" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns an Array containing the values of the header entry with the passed key" do + @headers["My-Header"] = "a" + @headers.get_fields("My-Header").should == ["a"] + + @headers.add_field("My-Header", "b") + @headers.get_fields("My-Header").should == ["a", "b"] + end + + it "returns a copy of the header entry values" do + @headers["My-Header"] = "a" + + @headers.get_fields("My-Header").clear + @headers.get_fields("My-Header").should == ["a"] + + @headers.get_fields("My-Header") << "b" + @headers.get_fields("My-Header").should == ["a"] + end + + it "returns nil for non-existing header entries" do + @headers.get_fields("My-Header").should be_nil + @headers.get_fields("My-Other-header").should be_nil + end + + it "is case-insensitive" do + @headers["My-Header"] = "test" + @headers.get_fields("My-Header").should == ["test"] + @headers.get_fields("my-header").should == ["test"] + @headers.get_fields("MY-HEADER").should == ["test"] + end +end diff --git a/spec/ruby/library/net-http/httpheader/initialize_http_header_spec.rb b/spec/ruby/library/net-http/httpheader/initialize_http_header_spec.rb new file mode 100644 index 0000000000..f9e6d208e5 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/initialize_http_header_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#initialize_http_header when passed Hash" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.allocate + end + + it "initializes the HTTP Header using the passed Hash" do + @headers.initialize_http_header("My-Header" => "test", "My-Other-Header" => "another test") + @headers["My-Header"].should == "test" + @headers["My-Other-Header"].should == "another test" + end + + it "complains about duplicate keys when in verbose mode" do + -> do + @headers.initialize_http_header("My-Header" => "test", "my-header" => "another test") + end.should complain(/duplicated HTTP header/, verbose: true) + end +end diff --git a/spec/ruby/library/net-http/httpheader/key_spec.rb b/spec/ruby/library/net-http/httpheader/key_spec.rb new file mode 100644 index 0000000000..2b7aeb9c2a --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/key_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#key? when passed key" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns true if the header entry with the passed key exists" do + @headers.key?("My-Header").should be_false + @headers["My-Header"] = "test" + @headers.key?("My-Header").should be_true + end + + it "is case-insensitive" do + @headers["My-Header"] = "test" + @headers.key?("my-header").should be_true + @headers.key?("MY-HEADER").should be_true + end +end diff --git a/spec/ruby/library/net-http/httpheader/length_spec.rb b/spec/ruby/library/net-http/httpheader/length_spec.rb new file mode 100644 index 0000000000..57e32742e4 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/length_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/size' + +describe "Net::HTTPHeader#length" do + it_behaves_like :net_httpheader_size, :length +end diff --git a/spec/ruby/library/net-http/httpheader/main_type_spec.rb b/spec/ruby/library/net-http/httpheader/main_type_spec.rb new file mode 100644 index 0000000000..4dd551d8f4 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/main_type_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#main_type" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns the 'main-content-type', as per 'Content-Type' header entry" do + @headers["Content-Type"] = "text/html" + @headers.main_type.should == "text" + + @headers["Content-Type"] = "application/pdf" + @headers.main_type.should == "application" + + @headers["Content-Type"] = "text/html;charset=utf-8" + @headers.main_type.should == "text" + end + + it "returns nil if the 'Content-Type' header entry does not exist" do + @headers.main_type.should be_nil + end +end diff --git a/spec/ruby/library/net-http/httpheader/proxy_basic_auth_spec.rb b/spec/ruby/library/net-http/httpheader/proxy_basic_auth_spec.rb new file mode 100644 index 0000000000..d9f6afc5a7 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/proxy_basic_auth_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#proxy_basic_auth when passed account, password" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "sets the 'Proxy-Authorization' Header entry for basic authorization" do + @headers.proxy_basic_auth("rubyspec", "rocks") + @headers["Proxy-Authorization"].should == "Basic cnVieXNwZWM6cm9ja3M=" + end +end diff --git a/spec/ruby/library/net-http/httpheader/range_length_spec.rb b/spec/ruby/library/net-http/httpheader/range_length_spec.rb new file mode 100644 index 0000000000..77323ac872 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/range_length_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#range_length" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns the length of the Range represented by the 'Content-Range' header entry" do + @headers["Content-Range"] = "bytes 0-499/1234" + @headers.range_length.should eql(500) + + @headers["Content-Range"] = "bytes 500-1233/1234" + @headers.range_length.should eql(734) + end + + it "returns nil when there is no 'Content-Range' header entry" do + @headers.range_length.should be_nil + end + + it "raises a Net::HTTPHeaderSyntaxError when the 'Content-Range' has an invalid format" do + @headers["Content-Range"] = "invalid" + -> { @headers.range_length }.should raise_error(Net::HTTPHeaderSyntaxError) + + @headers["Content-Range"] = "bytes 123-abc" + -> { @headers.range_length }.should raise_error(Net::HTTPHeaderSyntaxError) + + @headers["Content-Range"] = "bytes abc-123" + -> { @headers.range_length }.should raise_error(Net::HTTPHeaderSyntaxError) + end +end diff --git a/spec/ruby/library/net-http/httpheader/range_spec.rb b/spec/ruby/library/net-http/httpheader/range_spec.rb new file mode 100644 index 0000000000..2de80a825e --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/range_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/set_range' + +describe "Net::HTTPHeader#range" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns a Range object that represents the 'Range' header entry" do + @headers["Range"] = "bytes=0-499" + @headers.range.should == [0..499] + + @headers["Range"] = "bytes=500-1233" + @headers.range.should == [500..1233] + + @headers["Range"] = "bytes=10-" + @headers.range.should == [10..-1] + + @headers["Range"] = "bytes=-10" + @headers.range.should == [-10..-1] + end + + it "returns nil when there is no 'Range' header entry" do + @headers.range.should be_nil + end + + it "raises a Net::HTTPHeaderSyntaxError when the 'Range' has an invalid format" do + @headers["Range"] = "invalid" + -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError) + + @headers["Range"] = "bytes 123-abc" + -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError) + + @headers["Range"] = "bytes abc-123" + -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError) + end + + it "raises a Net::HTTPHeaderSyntaxError when the 'Range' was not specified" do + @headers["Range"] = "bytes=-" + -> { @headers.range }.should raise_error(Net::HTTPHeaderSyntaxError) + end +end + +describe "Net::HTTPHeader#range=" do + it_behaves_like :net_httpheader_set_range, :range= +end diff --git a/spec/ruby/library/net-http/httpheader/set_content_type_spec.rb b/spec/ruby/library/net-http/httpheader/set_content_type_spec.rb new file mode 100644 index 0000000000..7ec4f90b8e --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/set_content_type_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/set_content_type' + +describe "Net::HTTPHeader#set_content_type" do + it_behaves_like :net_httpheader_set_content_type, :set_content_type +end diff --git a/spec/ruby/library/net-http/httpheader/set_form_data_spec.rb b/spec/ruby/library/net-http/httpheader/set_form_data_spec.rb new file mode 100644 index 0000000000..7aac19f045 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/set_form_data_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/set_form_data' + +describe "Net::HTTPHeader#set_form_data" do + it_behaves_like :net_httpheader_set_form_data, :set_form_data +end diff --git a/spec/ruby/library/net-http/httpheader/set_range_spec.rb b/spec/ruby/library/net-http/httpheader/set_range_spec.rb new file mode 100644 index 0000000000..0f98de55e6 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/set_range_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/set_range' + +describe "Net::HTTPHeader#set_range" do + it_behaves_like :net_httpheader_set_range, :set_range +end diff --git a/spec/ruby/library/net-http/httpheader/shared/each_capitalized.rb b/spec/ruby/library/net-http/httpheader/shared/each_capitalized.rb new file mode 100644 index 0000000000..3bac409876 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/each_capitalized.rb @@ -0,0 +1,31 @@ +describe :net_httpheader_each_capitalized, shared: true do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + @headers["my-header"] = "test" + @headers.add_field("my-Other-Header", "a") + @headers.add_field("My-Other-header", "b") + end + + describe "when passed a block" do + it "yields each header entry to the passed block (capitalized keys, values joined)" do + res = [] + @headers.send(@method) do |key, value| + res << [key, value] + end + res.sort.should == [["My-Header", "test"], ["My-Other-Header", "a, b"]] + end + end + + describe "when passed no block" do + it "returns an Enumerator" do + enumerator = @headers.send(@method) + enumerator.should be_an_instance_of(Enumerator) + + res = [] + enumerator.each do |*key| + res << key + end + res.sort.should == [["My-Header", "test"], ["My-Other-Header", "a, b"]] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/shared/each_header.rb b/spec/ruby/library/net-http/httpheader/shared/each_header.rb new file mode 100644 index 0000000000..6bf3a6ddfe --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/each_header.rb @@ -0,0 +1,31 @@ +describe :net_httpheader_each_header, shared: true do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + @headers["My-Header"] = "test" + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + end + + describe "when passed a block" do + it "yields each header entry to the passed block (keys in lower case, values joined)" do + res = [] + @headers.send(@method) do |key, value| + res << [key, value] + end + res.sort.should == [["my-header", "test"], ["my-other-header", "a, b"]] + end + end + + describe "when passed no block" do + it "returns an Enumerator" do + enumerator = @headers.send(@method) + enumerator.should be_an_instance_of(Enumerator) + + res = [] + enumerator.each do |*key| + res << key + end + res.sort.should == [["my-header", "test"], ["my-other-header", "a, b"]] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/shared/each_name.rb b/spec/ruby/library/net-http/httpheader/shared/each_name.rb new file mode 100644 index 0000000000..efc6a09dfd --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/each_name.rb @@ -0,0 +1,31 @@ +describe :net_httpheader_each_name, shared: true do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + @headers["My-Header"] = "test" + @headers.add_field("My-Other-Header", "a") + @headers.add_field("My-Other-Header", "b") + end + + describe "when passed a block" do + it "yields each header key to the passed block (keys in lower case)" do + res = [] + @headers.send(@method) do |key| + res << key + end + res.sort.should == ["my-header", "my-other-header"] + end + end + + describe "when passed no block" do + it "returns an Enumerator" do + enumerator = @headers.send(@method) + enumerator.should be_an_instance_of(Enumerator) + + res = [] + enumerator.each do |key| + res << key + end + res.sort.should == ["my-header", "my-other-header"] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/shared/set_content_type.rb b/spec/ruby/library/net-http/httpheader/shared/set_content_type.rb new file mode 100644 index 0000000000..b7359bdca6 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/set_content_type.rb @@ -0,0 +1,18 @@ +describe :net_httpheader_set_content_type, shared: true do + describe "when passed type, params" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "sets the 'Content-Type' header entry based on the passed type and params" do + @headers.send(@method, "text/html") + @headers["Content-Type"].should == "text/html" + + @headers.send(@method, "text/html", "charset" => "utf-8") + @headers["Content-Type"].should == "text/html; charset=utf-8" + + @headers.send(@method, "text/html", "charset" => "utf-8", "rubyspec" => "rocks") + @headers["Content-Type"].split(/; /).sort.should == %w[charset=utf-8 rubyspec=rocks text/html] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/shared/set_form_data.rb b/spec/ruby/library/net-http/httpheader/shared/set_form_data.rb new file mode 100644 index 0000000000..db20b18803 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/set_form_data.rb @@ -0,0 +1,27 @@ +describe :net_httpheader_set_form_data, shared: true do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + describe "when passed params" do + it "automatically set the 'Content-Type' to 'application/x-www-form-urlencoded'" do + @headers.send(@method, "cmd" => "search", "q" => "ruby", "max" => "50") + @headers["Content-Type"].should == "application/x-www-form-urlencoded" + end + + it "sets self's body based on the passed form parameters" do + @headers.send(@method, "cmd" => "search", "q" => "ruby", "max" => "50") + @headers.body.split("&").sort.should == ["cmd=search", "max=50", "q=ruby"] + end + end + + describe "when passed params, separator" do + it "sets self's body based on the passed form parameters and the passed separator" do + @headers.send(@method, {"cmd" => "search", "q" => "ruby", "max" => "50"}, "&") + @headers.body.split("&").sort.should == ["cmd=search", "max=50", "q=ruby"] + + @headers.send(@method, {"cmd" => "search", "q" => "ruby", "max" => "50"}, ";") + @headers.body.split(";").sort.should == ["cmd=search", "max=50", "q=ruby"] + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/shared/set_range.rb b/spec/ruby/library/net-http/httpheader/shared/set_range.rb new file mode 100644 index 0000000000..87f51d46f3 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/set_range.rb @@ -0,0 +1,89 @@ +describe :net_httpheader_set_range, shared: true do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + describe "when passed nil" do + it "returns nil" do + @headers.send(@method, nil).should be_nil + end + + it "deletes the 'Range' header entry" do + @headers["Range"] = "bytes 0-499/1234" + @headers.send(@method, nil) + @headers["Range"].should be_nil + end + end + + describe "when passed Numeric" do + it "sets the 'Range' header entry based on the passed Numeric" do + @headers.send(@method, 10) + @headers["Range"].should == "bytes=0-9" + + @headers.send(@method, -10) + @headers["Range"].should == "bytes=-10" + + @headers.send(@method, 10.9) + @headers["Range"].should == "bytes=0-9" + end + end + + describe "when passed Range" do + it "sets the 'Range' header entry based on the passed Range" do + @headers.send(@method, 10..200) + @headers["Range"].should == "bytes=10-200" + + @headers.send(@method, 1..5) + @headers["Range"].should == "bytes=1-5" + + @headers.send(@method, 1...5) + @headers["Range"].should == "bytes=1-4" + + @headers.send(@method, 234..567) + @headers["Range"].should == "bytes=234-567" + + @headers.send(@method, -5..-1) + @headers["Range"].should == "bytes=-5" + + @headers.send(@method, 1..-1) + @headers["Range"].should == "bytes=1-" + end + + it "raises a Net::HTTPHeaderSyntaxError when the first Range element is negative" do + -> { @headers.send(@method, -10..5) }.should raise_error(Net::HTTPHeaderSyntaxError) + end + + it "raises a Net::HTTPHeaderSyntaxError when the last Range element is negative" do + -> { @headers.send(@method, 10..-5) }.should raise_error(Net::HTTPHeaderSyntaxError) + end + + it "raises a Net::HTTPHeaderSyntaxError when the last Range element is smaller than the first" do + -> { @headers.send(@method, 10..5) }.should raise_error(Net::HTTPHeaderSyntaxError) + end + end + + describe "when passed start, end" do + it "sets the 'Range' header entry based on the passed start and length values" do + @headers.send(@method, 10, 200) + @headers["Range"].should == "bytes=10-209" + + @headers.send(@method, 1, 5) + @headers["Range"].should == "bytes=1-5" + + @headers.send(@method, 234, 567) + @headers["Range"].should == "bytes=234-800" + end + + it "raises a Net::HTTPHeaderSyntaxError when start is negative" do + -> { @headers.send(@method, -10, 5) }.should raise_error(Net::HTTPHeaderSyntaxError) + end + + it "raises a Net::HTTPHeaderSyntaxError when start + length is negative" do + -> { @headers.send(@method, 10, -15) }.should raise_error(Net::HTTPHeaderSyntaxError) + end + + it "raises a Net::HTTPHeaderSyntaxError when length is negative" do + -> { @headers.send(@method, 10, -4) }.should raise_error(Net::HTTPHeaderSyntaxError) + end + end +end diff --git a/spec/ruby/library/net-http/httpheader/shared/size.rb b/spec/ruby/library/net-http/httpheader/shared/size.rb new file mode 100644 index 0000000000..e2b1e4c22b --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/shared/size.rb @@ -0,0 +1,18 @@ +describe :net_httpheader_size, shared: true do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns the number of header entries in self" do + @headers.send(@method).should eql(0) + + @headers["a"] = "b" + @headers.send(@method).should eql(1) + + @headers["b"] = "b" + @headers.send(@method).should eql(2) + + @headers["c"] = "c" + @headers.send(@method).should eql(3) + end +end diff --git a/spec/ruby/library/net-http/httpheader/size_spec.rb b/spec/ruby/library/net-http/httpheader/size_spec.rb new file mode 100644 index 0000000000..210060ce21 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/size_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' +require_relative 'shared/size' + +describe "Net::HTTPHeader#size" do + it_behaves_like :net_httpheader_size, :size +end diff --git a/spec/ruby/library/net-http/httpheader/sub_type_spec.rb b/spec/ruby/library/net-http/httpheader/sub_type_spec.rb new file mode 100644 index 0000000000..b39b57fe8d --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/sub_type_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#sub_type" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns the 'sub-content-type', as per 'Content-Type' header entry" do + @headers["Content-Type"] = "text/html" + @headers.sub_type.should == "html" + + @headers["Content-Type"] = "application/pdf" + @headers.sub_type.should == "pdf" + + @headers["Content-Type"] = "text/html;charset=utf-8" + @headers.sub_type.should == "html" + end + + it "returns nil if no 'sub-content-type' is set" do + @headers["Content-Type"] = "text" + @headers.sub_type.should be_nil + + @headers["Content-Type"] = "text;charset=utf-8" + @headers.sub_type.should be_nil + end + + it "returns nil if the 'Content-Type' header entry does not exist" do + @headers.sub_type.should be_nil + end +end diff --git a/spec/ruby/library/net-http/httpheader/to_hash_spec.rb b/spec/ruby/library/net-http/httpheader/to_hash_spec.rb new file mode 100644 index 0000000000..3cebc519a6 --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/to_hash_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#to_hash" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns a Hash representing all Header entries (keys in lower case, values as arrays)" do + @headers.to_hash.should == {} + + @headers["My-Header"] = "test" + @headers.to_hash.should == { "my-header" => ["test"] } + + @headers.add_field("My-Header", "another test") + @headers.to_hash.should == { "my-header" => ["test", "another test"] } + end + + it "does not allow modifying the headers from the returned hash" do + @headers.to_hash["my-header"] = ["test"] + @headers.to_hash.should == {} + @headers.key?("my-header").should be_false + end +end diff --git a/spec/ruby/library/net-http/httpheader/type_params_spec.rb b/spec/ruby/library/net-http/httpheader/type_params_spec.rb new file mode 100644 index 0000000000..ac97e2b48c --- /dev/null +++ b/spec/ruby/library/net-http/httpheader/type_params_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'fixtures/classes' + +describe "Net::HTTPHeader#type_params" do + before :each do + @headers = NetHTTPHeaderSpecs::Example.new + end + + it "returns additional 'Content-Type' information as a Hash" do + @headers["Content-Type"] = "text/html;charset=utf-8" + @headers.type_params.should == {"charset" => "utf-8"} + + @headers["Content-Type"] = "text/html; charset=utf-8; rubyspec=rocks" + @headers.type_params.should == {"charset" => "utf-8", "rubyspec" => "rocks"} + end + + it "returns an empty Hash when no additional 'Content-Type' information is set" do + @headers.type_params.should == {} + + @headers["Content-Type"] = "text/html" + @headers.type_params.should == {} + end +end diff --git a/spec/ruby/library/net-http/httprequest/initialize_spec.rb b/spec/ruby/library/net-http/httprequest/initialize_spec.rb new file mode 100644 index 0000000000..d009a00ed2 --- /dev/null +++ b/spec/ruby/library/net-http/httprequest/initialize_spec.rb @@ -0,0 +1,45 @@ +require_relative '../../../spec_helper' +require 'net/http' + +module NetHTTPRequestSpecs + class TestRequest < Net::HTTPRequest + METHOD = "TEST" + REQUEST_HAS_BODY = false + RESPONSE_HAS_BODY = true + end +end + +describe "Net::HTTPRequest#initialize" do + before :each do + @req = NetHTTPRequestSpecs::TestRequest.allocate + end + + it "uses the METHOD constants to set the request method" do + @req.send(:initialize, "/some/path") + @req.method.should == "TEST" + end + + it "uses the REQUEST_HAS_BODY to set whether the Request has a body or not" do + @req.send(:initialize, "/some/path") + @req.request_body_permitted?.should be_false + end + + it "uses the RESPONSE_HAS_BODY to set whether the Response can have a body or not" do + @req.send(:initialize, "/some/path") + @req.response_body_permitted?.should be_true + end + + describe "when passed path" do + it "sets self's path to the passed path" do + @req.send(:initialize, "/some/path") + @req.path.should == "/some/path" + end + end + + describe "when passed path, headers" do + it "uses the passed headers Hash to initialize self's header entries" do + @req.send(:initialize, "/some/path", "Content-Type" => "text/html") + @req["Content-Type"].should == "text/html" + end + end +end diff --git a/spec/ruby/library/net-http/httpresponse/body_permitted_spec.rb b/spec/ruby/library/net-http/httpresponse/body_permitted_spec.rb new file mode 100644 index 0000000000..68965de4a1 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/body_permitted_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse.body_permitted?" do + it "returns true if this response type can have a response body" do + Net::HTTPUnknownResponse.should.body_permitted? + Net::HTTPInformation.should_not.body_permitted? + Net::HTTPSuccess.should.body_permitted? + Net::HTTPRedirection.should.body_permitted? + Net::HTTPClientError.should.body_permitted? + Net::HTTPServerError.should.body_permitted? + end +end diff --git a/spec/ruby/library/net-http/httpresponse/body_spec.rb b/spec/ruby/library/net-http/httpresponse/body_spec.rb new file mode 100644 index 0000000000..ddfcd834c4 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/body_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'shared/body' + +describe "Net::HTTPResponse#body" do + it_behaves_like :net_httpresponse_body, :body +end diff --git a/spec/ruby/library/net-http/httpresponse/code_spec.rb b/spec/ruby/library/net-http/httpresponse/code_spec.rb new file mode 100644 index 0000000000..699062ad97 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/code_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#code" do + it "returns the result code string" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.code.should == "???" + + res = Net::HTTPInformation.new("1.0", "1xx", "test response") + res.code.should == "1xx" + + res = Net::HTTPSuccess.new("1.0", "2xx", "test response") + res.code.should == "2xx" + + res = Net::HTTPRedirection.new("1.0", "3xx", "test response") + res.code.should == "3xx" + + res = Net::HTTPClientError.new("1.0", "4xx", "test response") + res.code.should == "4xx" + + res = Net::HTTPServerError.new("1.0", "5xx", "test response") + res.code.should == "5xx" + end +end diff --git a/spec/ruby/library/net-http/httpresponse/code_type_spec.rb b/spec/ruby/library/net-http/httpresponse/code_type_spec.rb new file mode 100644 index 0000000000..beb661cbbe --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/code_type_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#code_type" do + it "returns self's class" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.code_type.should == Net::HTTPUnknownResponse + + res = Net::HTTPInformation.new("1.0", "1xx", "test response") + res.code_type.should == Net::HTTPInformation + + res = Net::HTTPSuccess.new("1.0", "2xx", "test response") + res.code_type.should == Net::HTTPSuccess + + res = Net::HTTPRedirection.new("1.0", "3xx", "test response") + res.code_type.should == Net::HTTPRedirection + + res = Net::HTTPClientError.new("1.0", "4xx", "test response") + res.code_type.should == Net::HTTPClientError + + res = Net::HTTPServerError.new("1.0", "5xx", "test response") + res.code_type.should == Net::HTTPServerError + end +end diff --git a/spec/ruby/library/net-http/httpresponse/entity_spec.rb b/spec/ruby/library/net-http/httpresponse/entity_spec.rb new file mode 100644 index 0000000000..ca8c4b29c0 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/entity_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require 'net/http' +require_relative 'shared/body' + +describe "Net::HTTPResponse#entity" do + it_behaves_like :net_httpresponse_body, :entity +end diff --git a/spec/ruby/library/net-http/httpresponse/error_spec.rb b/spec/ruby/library/net-http/httpresponse/error_spec.rb new file mode 100644 index 0000000000..6ced90fa23 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/error_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#error!" do + it "raises self's class 'EXCEPTION_TYPE' Exception" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + -> { res.error! }.should raise_error(Net::HTTPError) + + res = Net::HTTPInformation.new("1.0", "1xx", "test response") + -> { res.error! }.should raise_error(Net::HTTPError) + + res = Net::HTTPSuccess.new("1.0", "2xx", "test response") + -> { res.error! }.should raise_error(Net::HTTPError) + + res = Net::HTTPRedirection.new("1.0", "3xx", "test response") + -> { res.error! }.should raise_error(Net::HTTPRetriableError) + + res = Net::HTTPClientError.new("1.0", "4xx", "test response") + -> { res.error! }.should raise_error(Net::HTTPClientException) + + res = Net::HTTPServerError.new("1.0", "5xx", "test response") + -> { res.error! }.should raise_error(Net::HTTPFatalError) + end +end diff --git a/spec/ruby/library/net-http/httpresponse/error_type_spec.rb b/spec/ruby/library/net-http/httpresponse/error_type_spec.rb new file mode 100644 index 0000000000..3969621a5e --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/error_type_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#error_type" do + it "returns self's class 'EXCEPTION_TYPE' constant" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.error_type.should == Net::HTTPError + + res = Net::HTTPInformation.new("1.0", "1xx", "test response") + res.error_type.should == Net::HTTPError + + res = Net::HTTPSuccess.new("1.0", "2xx", "test response") + res.error_type.should == Net::HTTPError + + res = Net::HTTPRedirection.new("1.0", "3xx", "test response") + res.error_type.should == Net::HTTPRetriableError + + res = Net::HTTPClientError.new("1.0", "4xx", "test response") + res.error_type.should == Net::HTTPClientException + + res = Net::HTTPServerError.new("1.0", "5xx", "test response") + res.error_type.should == Net::HTTPFatalError + end +end diff --git a/spec/ruby/library/net-http/httpresponse/exception_type_spec.rb b/spec/ruby/library/net-http/httpresponse/exception_type_spec.rb new file mode 100644 index 0000000000..dd2761a744 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/exception_type_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse.exception_type" do + it "returns self's 'EXCEPTION_TYPE' constant" do + Net::HTTPUnknownResponse.exception_type.should == Net::HTTPError + Net::HTTPInformation.exception_type.should == Net::HTTPError + Net::HTTPSuccess.exception_type.should == Net::HTTPError + Net::HTTPRedirection.exception_type.should == Net::HTTPRetriableError + Net::HTTPClientError.exception_type.should == Net::HTTPClientException + Net::HTTPServerError.exception_type.should == Net::HTTPFatalError + end +end diff --git a/spec/ruby/library/net-http/httpresponse/header_spec.rb b/spec/ruby/library/net-http/httpresponse/header_spec.rb new file mode 100644 index 0000000000..a403dbd2c3 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/header_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#header" do + it "returns self" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.response.should equal(res) + end +end diff --git a/spec/ruby/library/net-http/httpresponse/http_version_spec.rb b/spec/ruby/library/net-http/httpresponse/http_version_spec.rb new file mode 100644 index 0000000000..a3e413a360 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/http_version_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#http_version" do + it "returns self's http version" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.http_version.should == "1.0" + + res = Net::HTTPUnknownResponse.new("1.1", "???", "test response") + res.http_version.should == "1.1" + end +end diff --git a/spec/ruby/library/net-http/httpresponse/initialize_spec.rb b/spec/ruby/library/net-http/httpresponse/initialize_spec.rb new file mode 100644 index 0000000000..673c11a245 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/initialize_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#initialize when passed http_version, response_code, response_message" do + it "sets self http_version, response_code and response_message to the passed values" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.http_version.should == "1.0" + res.code.should == "???" + res.message.should == "test response" + end +end diff --git a/spec/ruby/library/net-http/httpresponse/inspect_spec.rb b/spec/ruby/library/net-http/httpresponse/inspect_spec.rb new file mode 100644 index 0000000000..43071ec8cd --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/inspect_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' +require 'net/http' +require "stringio" + +describe "Net::HTTPResponse#inspect" do + it "returns a String representation of self" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.inspect.should == "#<Net::HTTPUnknownResponse ??? test response readbody=false>" + + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + socket = Net::BufferedIO.new(StringIO.new("test body")) + res.reading_body(socket, true) {} + res.inspect.should == "#<Net::HTTPUnknownResponse ??? test response readbody=true>" + end +end diff --git a/spec/ruby/library/net-http/httpresponse/message_spec.rb b/spec/ruby/library/net-http/httpresponse/message_spec.rb new file mode 100644 index 0000000000..5ba73bb449 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/message_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#message" do + it "returns self's response message" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.message.should == "test response" + end +end diff --git a/spec/ruby/library/net-http/httpresponse/msg_spec.rb b/spec/ruby/library/net-http/httpresponse/msg_spec.rb new file mode 100644 index 0000000000..04f5836d7a --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/msg_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#msg" do + it "returns self's response message" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.message.should == "test response" + end +end diff --git a/spec/ruby/library/net-http/httpresponse/read_body_spec.rb b/spec/ruby/library/net-http/httpresponse/read_body_spec.rb new file mode 100644 index 0000000000..4530a26bfc --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/read_body_spec.rb @@ -0,0 +1,86 @@ +require_relative '../../../spec_helper' +require 'net/http' +require 'stringio' + +describe "Net::HTTPResponse#read_body" do + before :each do + @res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + @socket = Net::BufferedIO.new(StringIO.new("test body")) + end + + describe "when passed no arguments" do + it "returns the read body" do + @res.reading_body(@socket, true) do + @res.read_body.should == "test body" + end + end + + it "returns the previously read body if called a second time" do + @res.reading_body(@socket, true) do + @res.read_body.should equal(@res.read_body) + end + end + end + + describe "when passed a buffer" do + it "reads the body to the passed buffer" do + @res.reading_body(@socket, true) do + buffer = +"" + @res.read_body(buffer) + buffer.should == "test body" + end + end + + it "returns the passed buffer" do + @res.reading_body(@socket, true) do + buffer = +"" + @res.read_body(buffer).should equal(buffer) + end + end + + it "raises an IOError if called a second time" do + @res.reading_body(@socket, true) do + @res.read_body(+"") + -> { @res.read_body(+"") }.should raise_error(IOError) + end + end + end + + describe "when passed a block" do + it "reads the body and yields it to the passed block (in chunks)" do + @res.reading_body(@socket, true) do + yielded = false + + buffer = +"" + @res.read_body do |body| + yielded = true + buffer << body + end + + yielded.should be_true + buffer.should == "test body" + end + end + + it "returns the ReadAdapter" do + @res.reading_body(@socket, true) do + @res.read_body { nil }.should be_kind_of(Net::ReadAdapter) + end + end + + it "raises an IOError if called a second time" do + @res.reading_body(@socket, true) do + @res.read_body {} + -> { @res.read_body {} }.should raise_error(IOError) + end + end + end + + describe "when passed buffer and block" do + it "raises an ArgumentError" do + @res.reading_body(@socket, true) do + -> { @res.read_body(+"") {} }.should raise_error(ArgumentError) + end + end + end +end diff --git a/spec/ruby/library/net-http/httpresponse/read_header_spec.rb b/spec/ruby/library/net-http/httpresponse/read_header_spec.rb new file mode 100644 index 0000000000..3ea4ee834b --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/read_header_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#read_header" do + it "returns self" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.response.should equal(res) + end +end diff --git a/spec/ruby/library/net-http/httpresponse/read_new_spec.rb b/spec/ruby/library/net-http/httpresponse/read_new_spec.rb new file mode 100644 index 0000000000..82f7a47ce8 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/read_new_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../../spec_helper' +require 'net/http' +require 'stringio' + +describe "Net::HTTPResponse.read_new" do + it "creates a HTTPResponse object based on the response read from the passed socket" do + socket = Net::BufferedIO.new(StringIO.new(<<EOS)) +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 + +test-body +EOS + response = Net::HTTPResponse.read_new(socket) + + response.should be_kind_of(Net::HTTPOK) + response.code.should == "200" + response["Content-Type"].should == "text/html; charset=utf-8" + + response.reading_body(socket, true) do + response.body.should == "test-body\n" + end + end +end diff --git a/spec/ruby/library/net-http/httpresponse/reading_body_spec.rb b/spec/ruby/library/net-http/httpresponse/reading_body_spec.rb new file mode 100644 index 0000000000..637a2806f8 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/reading_body_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../../spec_helper' +require 'net/http' +require "stringio" + +describe "Net::HTTPResponse#reading_body" do + before :each do + @res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + @socket = Net::BufferedIO.new(StringIO.new("test body")) + end + + describe "when body_allowed is true" do + it "reads and returns the response body for self from the passed socket" do + @res.reading_body(@socket, true) {}.should == "test body" + @res.body.should == "test body" + end + + it "yields the passed block before reading the body" do + yielded = false + + @res.reading_body(@socket, true) do + @res.inspect.should == "#<Net::HTTPUnknownResponse ??? test response readbody=false>" + yielded = true + end + + yielded.should be_true + end + + describe "but the response type is not allowed to have a body" do + before :each do + @res = Net::HTTPInformation.new("1.0", "???", "test response") + end + + it "returns nil" do + @res.reading_body(@socket, false) {}.should be_nil + @res.body.should be_nil + end + + it "yields the passed block" do + yielded = false + @res.reading_body(@socket, true) { yielded = true } + yielded.should be_true + end + end + end + + describe "when body_allowed is false" do + it "returns nil" do + @res.reading_body(@socket, false) {}.should be_nil + @res.body.should be_nil + end + + it "yields the passed block" do + yielded = false + @res.reading_body(@socket, true) { yielded = true } + yielded.should be_true + end + end +end diff --git a/spec/ruby/library/net-http/httpresponse/response_spec.rb b/spec/ruby/library/net-http/httpresponse/response_spec.rb new file mode 100644 index 0000000000..caa0ca2d19 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/response_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#response" do + it "returns self" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + res.response.should equal(res) + end +end diff --git a/spec/ruby/library/net-http/httpresponse/shared/body.rb b/spec/ruby/library/net-http/httpresponse/shared/body.rb new file mode 100644 index 0000000000..618e3936fb --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/shared/body.rb @@ -0,0 +1,20 @@ +require 'stringio' + +describe :net_httpresponse_body, shared: true do + before :each do + @res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + @socket = Net::BufferedIO.new(StringIO.new("test body")) + end + + it "returns the read body" do + @res.reading_body(@socket, true) do + @res.send(@method).should == "test body" + end + end + + it "returns the previously read body if called a second time" do + @res.reading_body(@socket, true) do + @res.send(@method).should equal(@res.send(@method)) + end + end +end diff --git a/spec/ruby/library/net-http/httpresponse/value_spec.rb b/spec/ruby/library/net-http/httpresponse/value_spec.rb new file mode 100644 index 0000000000..2df8beaa10 --- /dev/null +++ b/spec/ruby/library/net-http/httpresponse/value_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'net/http' + +describe "Net::HTTPResponse#value" do + it "raises an HTTP error for non 2xx HTTP Responses" do + res = Net::HTTPUnknownResponse.new("1.0", "???", "test response") + -> { res.value }.should raise_error(Net::HTTPError) + + res = Net::HTTPInformation.new("1.0", "1xx", "test response") + -> { res.value }.should raise_error(Net::HTTPError) + + res = Net::HTTPSuccess.new("1.0", "2xx", "test response") + -> { res.value }.should_not raise_error(Net::HTTPError) + + res = Net::HTTPRedirection.new("1.0", "3xx", "test response") + -> { res.value }.should raise_error(Net::HTTPRetriableError) + + res = Net::HTTPClientError.new("1.0", "4xx", "test response") + -> { res.value }.should raise_error(Net::HTTPClientException) + + res = Net::HTTPServerError.new("1.0", "5xx", "test response") + -> { res.value }.should raise_error(Net::HTTPFatalError) + end +end diff --git a/spec/ruby/library/objectspace/dump_all_spec.rb b/spec/ruby/library/objectspace/dump_all_spec.rb new file mode 100644 index 0000000000..e9b449a905 --- /dev/null +++ b/spec/ruby/library/objectspace/dump_all_spec.rb @@ -0,0 +1,112 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.dump_all" do + it "dumps Ruby heap to string when passed output: :string" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + dump = ObjectSpace.dump_all(output: :string) + puts dump.class + puts dump.include?('"value":"abc"') + RUBY + + stdout.should == "String\ntrue\n" + end + + it "dumps Ruby heap to a temporary file when passed output: :file" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + file = ObjectSpace.dump_all(output: :file) + + begin + file.flush + file.rewind + content = file.read + + puts file.class + puts content.include?('"value":"abc"') + ensure + file.close + File.unlink file.path + end + RUBY + + stdout.should == "File\ntrue\n" + end + + it "dumps Ruby heap to a temporary file when :output not specified" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + file = ObjectSpace.dump_all + + begin + file.flush + file.rewind + content = file.read + + puts file.class + puts content.include?('"value":"abc"') + ensure + file.close + File.unlink file.path + end + RUBY + + stdout.should == "File\ntrue\n" + end + + it "dumps Ruby heap to a temporary file when passed output: :nil" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + file = ObjectSpace.dump_all(output: nil) + + begin + file.flush + file.rewind + content = file.read + + puts file.class + puts content.include?('"value":"abc"') + ensure + file.close + File.unlink file.path + end + RUBY + + stdout.should == "File\ntrue\n" + end + + it "dumps Ruby heap to stdout when passed output: :stdout" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + ObjectSpace.dump_all(output: :stdout) + RUBY + + stdout.should include('"value":"abc"') + end + + it "dumps Ruby heap to provided IO when passed output: IO" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace -rtempfile") + string = "abc" + io = Tempfile.create("object_space_dump_all") + + begin + result = ObjectSpace.dump_all(output: io) + io.rewind + content = io.read + + puts result.equal?(io) + puts content.include?('"value":"abc"') + ensure + io.close + File.unlink io.path + end + RUBY + + stdout.should == "true\ntrue\n" + end + + it "raises ArgumentError when passed not supported :output value" do + -> { ObjectSpace.dump_all(output: Object.new) }.should raise_error(ArgumentError, /wrong output option/) + end +end diff --git a/spec/ruby/library/objectspace/dump_spec.rb b/spec/ruby/library/objectspace/dump_spec.rb new file mode 100644 index 0000000000..e22ee3df1e --- /dev/null +++ b/spec/ruby/library/objectspace/dump_spec.rb @@ -0,0 +1,70 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.dump" do + it "dumps the content of object as JSON" do + require 'json' + string = ObjectSpace.dump("abc") + dump = JSON.parse(string) + + dump['type'].should == "STRING" + dump['value'].should == "abc" + end + + it "dumps to string when passed output: :string" do + string = ObjectSpace.dump("abc", output: :string) + string.should be_kind_of(String) + string.should include('"value":"abc"') + end + + it "dumps to string when :output not specified" do + string = ObjectSpace.dump("abc") + string.should be_kind_of(String) + string.should include('"value":"abc"') + end + + it "dumps to a temporary file when passed output: :file" do + file = ObjectSpace.dump("abc", output: :file) + file.should be_kind_of(File) + + file.rewind + content = file.read + content.should include('"value":"abc"') + ensure + file.close + File.unlink file.path + end + + it "dumps to a temporary file when passed output: :nil" do + file = ObjectSpace.dump("abc", output: nil) + file.should be_kind_of(File) + + file.rewind + file.read.should include('"value":"abc"') + ensure + file.close + File.unlink file.path + end + + it "dumps to stdout when passed output: :stdout" do + stdout = ruby_exe('ObjectSpace.dump("abc", output: :stdout)', options: "-robjspace").chomp + stdout.should include('"value":"abc"') + end + + it "dumps to provided IO when passed output: IO" do + filename = tmp("io_read.txt") + io = File.open(filename, "w+") + result = ObjectSpace.dump("abc", output: io) + result.should.equal? io + + io.rewind + io.read.should include('"value":"abc"') + ensure + io.close + rm_r filename + end + + it "raises ArgumentError when passed not supported :output value" do + -> { ObjectSpace.dump("abc", output: Object.new) }.should raise_error(ArgumentError, /wrong output option/) + end +end diff --git a/spec/ruby/library/objectspace/fixtures/trace.rb b/spec/ruby/library/objectspace/fixtures/trace.rb new file mode 100644 index 0000000000..e53a7a0cac --- /dev/null +++ b/spec/ruby/library/objectspace/fixtures/trace.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: false +require "objspace/trace" +a = "foo" +b = "b" + "a" + "r" +c = 42 +p a, b, c diff --git a/spec/ruby/library/objectspace/memsize_of_all_spec.rb b/spec/ruby/library/objectspace/memsize_of_all_spec.rb new file mode 100644 index 0000000000..c5a48165ce --- /dev/null +++ b/spec/ruby/library/objectspace/memsize_of_all_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.memsize_of_all" do + it "returns a non-zero Integer for all objects" do + ObjectSpace.memsize_of_all.should be_kind_of(Integer) + ObjectSpace.memsize_of_all.should > 0 + end + + it "returns a non-zero Integer for Class" do + ObjectSpace.memsize_of_all(Class).should be_kind_of(Integer) + ObjectSpace.memsize_of_all(Class).should > 0 + end + + it "increases when a new object is allocated" do + c = Class.new + before = ObjectSpace.memsize_of_all(c) + o = c.new + after = ObjectSpace.memsize_of_all(c) + after.should > before + end +end diff --git a/spec/ruby/library/objectspace/memsize_of_spec.rb b/spec/ruby/library/objectspace/memsize_of_spec.rb new file mode 100644 index 0000000000..cbb5a07d54 --- /dev/null +++ b/spec/ruby/library/objectspace/memsize_of_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.memsize_of" do + it "returns 0 for true, false and nil" do + ObjectSpace.memsize_of(true).should == 0 + ObjectSpace.memsize_of(false).should == 0 + ObjectSpace.memsize_of(nil).should == 0 + end + + it "returns 0 for small Integers" do + ObjectSpace.memsize_of(42).should == 0 + end + + it "returns 0 for literal Symbols" do + ObjectSpace.memsize_of(:object_space_memsize_spec_static_sym).should == 0 + end + + it "returns a positive Integer for an Object" do + obj = Object.new + ObjectSpace.memsize_of(obj).should be_kind_of(Integer) + ObjectSpace.memsize_of(obj).should > 0 + end + + it "is larger if the Object has more instance variables" do + obj = Object.new + before = ObjectSpace.memsize_of(obj) + 100.times do |i| + obj.instance_variable_set(:"@foo#{i}", nil) + end + after = ObjectSpace.memsize_of(obj) + after.should > before + end +end diff --git a/spec/ruby/library/objectspace/reachable_objects_from_spec.rb b/spec/ruby/library/objectspace/reachable_objects_from_spec.rb new file mode 100644 index 0000000000..dee5961663 --- /dev/null +++ b/spec/ruby/library/objectspace/reachable_objects_from_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.reachable_objects_from" do + it "returns nil for true and false" do + ObjectSpace.reachable_objects_from(true).should == nil + ObjectSpace.reachable_objects_from(false).should == nil + end + + it "returns nil for nil" do + ObjectSpace.reachable_objects_from(nil).should == nil + end + + it "returns nil for small Integers" do + ObjectSpace.reachable_objects_from(42).should == nil + end + + it "enumerates objects directly reachable from a given object" do + ObjectSpace.reachable_objects_from(['a', 'b', 'c']).should include(Array, 'a', 'b', 'c') + ObjectSpace.reachable_objects_from(Object.new).should == [Object] + end + + it "finds an object stored in an Array" do + obj = Object.new + ary = [obj] + reachable = ObjectSpace.reachable_objects_from(ary) + reachable.should include(obj) + end + + it "finds an object stored in a copy-on-write Array" do + removed = Object.new + obj = Object.new + ary = [removed, obj] + ary.shift + reachable = ObjectSpace.reachable_objects_from(ary) + reachable.should include(obj) + reachable.should_not include(removed) + end + + it "finds an object stored in a Queue" do + o = Object.new + q = Queue.new + q << o + + reachable = ObjectSpace.reachable_objects_from(q) + reachable = reachable + reachable.flat_map { |r| ObjectSpace.reachable_objects_from(r) } + reachable.should include(o) + end + + it "finds an object stored in a SizedQueue" do + o = Object.new + q = SizedQueue.new(3) + q << o + + reachable = ObjectSpace.reachable_objects_from(q) + reachable = reachable + reachable.flat_map { |r| ObjectSpace.reachable_objects_from(r) } + reachable.should include(o) + end +end diff --git a/spec/ruby/library/objectspace/trace_object_allocations_spec.rb b/spec/ruby/library/objectspace/trace_object_allocations_spec.rb new file mode 100644 index 0000000000..0f1e2aa8b9 --- /dev/null +++ b/spec/ruby/library/objectspace/trace_object_allocations_spec.rb @@ -0,0 +1,163 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.trace_object_allocations" do + def has_class_frame? + Class.new { + attr_reader :c + + def initialize + @c = caller_locations.first.label =~ /new/ + end + }.new.c + end + + def obj_class_path + has_class_frame? ? "Class" : nil + end + + it "runs a block" do + ScratchPad.clear + ObjectSpace.trace_object_allocations do + ScratchPad.record :a + end + ScratchPad.recorded.should == :a + end + + it "records info for allocation_class_path" do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_class_path(o).should == obj_class_path + a = [1, 2, 3] + ObjectSpace.allocation_class_path(a).should == nil + end + end + + it "records info for allocation_generation" do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_generation(o).should.kind_of?(Integer) + a = [1, 2, 3] + ObjectSpace.allocation_generation(a).should.kind_of?(Integer) + end + end + + it "records info for allocation_method_id" do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_method_id(o).should == (has_class_frame? ? :new : nil) + a = [1, 2, 3] + ObjectSpace.allocation_method_id(a).should == nil + end + end + + it "records info for allocation_sourcefile" do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_sourcefile(o).should == __FILE__ + a = [1, 2, 3] + ObjectSpace.allocation_sourcefile(a).should == __FILE__ + end + end + + it "records info for allocation_sourceline" do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_sourceline(o).should == __LINE__ - 1 + a = [1, 2, 3] + ObjectSpace.allocation_sourceline(a).should == __LINE__ - 1 + end + end + + it "can be cleared using trace_object_allocations_clear" do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_class_path(o).should == obj_class_path + ObjectSpace.trace_object_allocations_clear + ObjectSpace.allocation_class_path(o).should be_nil + end + end + + it "does not clears allocation data after returning" do + o = nil + ObjectSpace.trace_object_allocations do + o = Object.new + end + ObjectSpace.allocation_class_path(o).should == obj_class_path + end + + it "can be used without a block using trace_object_allocations_start and _stop" do + ObjectSpace.trace_object_allocations_start + begin + o = Object.new + ObjectSpace.allocation_class_path(o).should == obj_class_path + a = [1, 2, 3] + ObjectSpace.allocation_class_path(a).should == nil + ensure + ObjectSpace.trace_object_allocations_stop + end + end + + it "does not clears allocation data after trace_object_allocations_stop" do + ObjectSpace.trace_object_allocations_start + begin + o = Object.new + ensure + ObjectSpace.trace_object_allocations_stop + end + ObjectSpace.allocation_class_path(o).should == obj_class_path + end + + it "can be nested" do + ObjectSpace.trace_object_allocations do + ObjectSpace.trace_object_allocations do + o = Object.new + ObjectSpace.allocation_class_path(o).should == obj_class_path + end + end + end + + it "can be nested without a block using trace_object_allocations_start and _stop" do + ObjectSpace.trace_object_allocations_start + begin + ObjectSpace.trace_object_allocations_start + begin + o = Object.new + ObjectSpace.allocation_class_path(o).should == obj_class_path + ensure + ObjectSpace.trace_object_allocations_stop + end + ensure + ObjectSpace.trace_object_allocations_stop + end + end + + it "can be nested with more _stop than _start" do + ObjectSpace.trace_object_allocations_start + begin + o = Object.new + ObjectSpace.allocation_class_path(o).should == obj_class_path + ObjectSpace.trace_object_allocations_stop + ensure + ObjectSpace.trace_object_allocations_stop + end + end + + it "returns nil for class_path, generation, method_id, sourcefile, and sourceline for immutable objects" do + ObjectSpace.trace_object_allocations_start + begin + one = nil + two = 42 + three = :foo + [one, two, three].each do |i| + ObjectSpace.allocation_class_path(i).should == nil + ObjectSpace.allocation_generation(i).should == nil + ObjectSpace.allocation_method_id(i).should == nil + ObjectSpace.allocation_sourcefile(i).should == nil + ObjectSpace.allocation_sourceline(i).should == nil + end + ensure + ObjectSpace.trace_object_allocations_stop + end + end +end diff --git a/spec/ruby/library/objectspace/trace_spec.rb b/spec/ruby/library/objectspace/trace_spec.rb new file mode 100644 index 0000000000..3957dc930d --- /dev/null +++ b/spec/ruby/library/objectspace/trace_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' + +describe 'require "objspace/trace"' do + it "shows object allocation sites" do + file = fixture(__FILE__ , "trace.rb") + ruby_exe(file, args: "2>&1").lines(chomp: true).should == [ + "objspace/trace is enabled", + "\"foo\" @ #{file}:3", + "\"bar\" @ #{file}:4", + "42" + ] + end +end diff --git a/spec/ruby/library/observer/add_observer_spec.rb b/spec/ruby/library/observer/add_observer_spec.rb new file mode 100644 index 0000000000..5217ae6dc4 --- /dev/null +++ b/spec/ruby/library/observer/add_observer_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Observer#add_observer" do + + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + end + + it "adds the observer" do + @observer.value.should == nil + @observable.changed + @observable.notify_observers("test") + @observer.value.should == nil + + @observable.add_observer(@observer) + @observable.changed + @observable.notify_observers("test2") + @observer.value.should == "test2" + end + +end diff --git a/spec/ruby/library/observer/count_observers_spec.rb b/spec/ruby/library/observer/count_observers_spec.rb new file mode 100644 index 0000000000..c93674196d --- /dev/null +++ b/spec/ruby/library/observer/count_observers_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Observer#count_observers" do + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + @observer2 = ObserverCallbackSpecs.new + end + + it "returns the number of observers" do + @observable.count_observers.should == 0 + @observable.add_observer(@observer) + @observable.count_observers.should == 1 + @observable.add_observer(@observer2) + @observable.count_observers.should == 2 + end + + it "returns the number of unique observers" do + 2.times { @observable.add_observer(@observer) } + @observable.count_observers.should == 1 + end +end diff --git a/spec/ruby/library/observer/delete_observer_spec.rb b/spec/ruby/library/observer/delete_observer_spec.rb new file mode 100644 index 0000000000..52be1a6cba --- /dev/null +++ b/spec/ruby/library/observer/delete_observer_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Observer#delete_observer" do + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + end + + it "deletes the observer" do + @observable.add_observer(@observer) + @observable.delete_observer(@observer) + + @observable.changed + @observable.notify_observers("test") + @observer.value.should == nil + end + +end diff --git a/spec/ruby/library/observer/delete_observers_spec.rb b/spec/ruby/library/observer/delete_observers_spec.rb new file mode 100644 index 0000000000..186e93a013 --- /dev/null +++ b/spec/ruby/library/observer/delete_observers_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Observer#delete_observers" do + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + end + + it "deletes the observers" do + @observable.add_observer(@observer) + @observable.delete_observers + + @observable.changed + @observable.notify_observers("test") + @observer.value.should == nil + end + +end diff --git a/spec/ruby/library/observer/fixtures/classes.rb b/spec/ruby/library/observer/fixtures/classes.rb new file mode 100644 index 0000000000..70cd1b1be2 --- /dev/null +++ b/spec/ruby/library/observer/fixtures/classes.rb @@ -0,0 +1,17 @@ +require 'observer' + +class ObserverCallbackSpecs + attr_reader :value + + def initialize + @value = nil + end + + def update(value) + @value = value + end +end + +class ObservableSpecs + include Observable +end diff --git a/spec/ruby/library/observer/notify_observers_spec.rb b/spec/ruby/library/observer/notify_observers_spec.rb new file mode 100644 index 0000000000..31f82e9266 --- /dev/null +++ b/spec/ruby/library/observer/notify_observers_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Observer#notify_observers" do + + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + @observable.add_observer(@observer) + end + + it "must call changed before notifying observers" do + @observer.value.should == nil + @observable.notify_observers("test") + @observer.value.should == nil + end + + it "verifies observer responds to update" do + -> { + @observable.add_observer(@observable) + }.should raise_error(NoMethodError) + end + + it "receives the callback" do + @observer.value.should == nil + @observable.changed + @observable.notify_observers("test") + @observer.value.should == "test" + end + +end diff --git a/spec/ruby/library/open3/capture2_spec.rb b/spec/ruby/library/open3/capture2_spec.rb new file mode 100644 index 0000000000..f707281d7f --- /dev/null +++ b/spec/ruby/library/open3/capture2_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.capture2" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/capture2e_spec.rb b/spec/ruby/library/open3/capture2e_spec.rb new file mode 100644 index 0000000000..7dd42f3491 --- /dev/null +++ b/spec/ruby/library/open3/capture2e_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.capture2e" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/capture3_spec.rb b/spec/ruby/library/open3/capture3_spec.rb new file mode 100644 index 0000000000..55c858c03f --- /dev/null +++ b/spec/ruby/library/open3/capture3_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.capture3" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/pipeline_r_spec.rb b/spec/ruby/library/open3/pipeline_r_spec.rb new file mode 100644 index 0000000000..e1b476f856 --- /dev/null +++ b/spec/ruby/library/open3/pipeline_r_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.pipeline_r" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/pipeline_rw_spec.rb b/spec/ruby/library/open3/pipeline_rw_spec.rb new file mode 100644 index 0000000000..8d889a200a --- /dev/null +++ b/spec/ruby/library/open3/pipeline_rw_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.pipeline_rw" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/pipeline_spec.rb b/spec/ruby/library/open3/pipeline_spec.rb new file mode 100644 index 0000000000..5dc628dcaf --- /dev/null +++ b/spec/ruby/library/open3/pipeline_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.pipeline" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/pipeline_start_spec.rb b/spec/ruby/library/open3/pipeline_start_spec.rb new file mode 100644 index 0000000000..af426807fe --- /dev/null +++ b/spec/ruby/library/open3/pipeline_start_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.pipeline_start" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/pipeline_w_spec.rb b/spec/ruby/library/open3/pipeline_w_spec.rb new file mode 100644 index 0000000000..0c2a3ca4e7 --- /dev/null +++ b/spec/ruby/library/open3/pipeline_w_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.pipeline_w" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/popen2_spec.rb b/spec/ruby/library/open3/popen2_spec.rb new file mode 100644 index 0000000000..a1a251660a --- /dev/null +++ b/spec/ruby/library/open3/popen2_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.popen2" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/popen2e_spec.rb b/spec/ruby/library/open3/popen2e_spec.rb new file mode 100644 index 0000000000..e65607160c --- /dev/null +++ b/spec/ruby/library/open3/popen2e_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.popen2e" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/open3/popen3_spec.rb b/spec/ruby/library/open3/popen3_spec.rb new file mode 100644 index 0000000000..d3103ad3cb --- /dev/null +++ b/spec/ruby/library/open3/popen3_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require 'open3' + +describe "Open3.popen3" do + it "returns in, out, err and a thread waiting the process" do + stdin, out, err, waiter = Open3.popen3(ruby_cmd("print :foo")) + begin + stdin.should be_kind_of IO + out.should be_kind_of IO + err.should be_kind_of IO + waiter.should be_kind_of Thread + + out.read.should == "foo" + ensure + stdin.close + out.close + err.close + waiter.join + end + end + + it "executes a process with a pipe to read stdout" do + Open3.popen3(ruby_cmd("print :foo")) do |stdin, out, err| + out.read.should == "foo" + end + end + + it "executes a process with a pipe to read stderr" do + Open3.popen3(ruby_cmd("STDERR.print :foo")) do |stdin, out, err| + err.read.should == "foo" + end + end + + it "executes a process with a pipe to write stdin" do + Open3.popen3(ruby_cmd("print STDIN.read")) do |stdin, out, err| + stdin.write("foo") + stdin.close + out.read.should == "foo" + end + end +end diff --git a/spec/ruby/library/openssl/cipher_spec.rb b/spec/ruby/library/openssl/cipher_spec.rb new file mode 100644 index 0000000000..f66f25f9c6 --- /dev/null +++ b/spec/ruby/library/openssl/cipher_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'shared/constants' +require 'openssl' + +describe "OpenSSL::Cipher's CipherError" do + it "exists under OpenSSL::Cipher namespace" do + OpenSSL::Cipher.should have_constant :CipherError + end +end diff --git a/spec/ruby/library/openssl/digest/append_spec.rb b/spec/ruby/library/openssl/digest/append_spec.rb new file mode 100644 index 0000000000..08802b7253 --- /dev/null +++ b/spec/ruby/library/openssl/digest/append_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/update' + +describe "OpenSSL::Digest#<<" do + it_behaves_like :openssl_digest_update, :<< +end diff --git a/spec/ruby/library/openssl/digest/block_length_spec.rb b/spec/ruby/library/openssl/digest/block_length_spec.rb new file mode 100644 index 0000000000..444ed9d20d --- /dev/null +++ b/spec/ruby/library/openssl/digest/block_length_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#block_length" do + context "when the digest object is created via a name argument" do + it "returns a SHA1 block length" do + OpenSSL::Digest.new('sha1').block_length.should == SHA1Constants::BlockLength + end + + it "returns a SHA256 block length" do + OpenSSL::Digest.new('sha256').block_length.should == SHA256Constants::BlockLength + end + + it "returns a SHA384 block length" do + OpenSSL::Digest.new('sha384').block_length.should == SHA384Constants::BlockLength + end + + it "returns a SHA512 block length" do + OpenSSL::Digest.new('sha512').block_length.should == SHA512Constants::BlockLength + end + end + + context "when the digest object is created via a subclass" do + it "returns a SHA1 block length" do + OpenSSL::Digest::SHA1.new.block_length.should == SHA1Constants::BlockLength + end + + it "returns a SHA256 block length" do + OpenSSL::Digest::SHA256.new.block_length.should == SHA256Constants::BlockLength + end + + it "returns a SHA384 block length" do + OpenSSL::Digest::SHA384.new.block_length.should == SHA384Constants::BlockLength + end + + it "returns a SHA512 block length" do + OpenSSL::Digest::SHA512.new.block_length.should == SHA512Constants::BlockLength + end + end +end diff --git a/spec/ruby/library/openssl/digest/digest_length_spec.rb b/spec/ruby/library/openssl/digest/digest_length_spec.rb new file mode 100644 index 0000000000..37d1cba9a7 --- /dev/null +++ b/spec/ruby/library/openssl/digest/digest_length_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#digest_length" do + context "when the digest object is created via a name argument" do + it "returns a SHA1 digest length" do + OpenSSL::Digest.new('sha1').digest_length.should == SHA1Constants::DigestLength + end + + it "returns a SHA256 digest length" do + OpenSSL::Digest.new('sha256').digest_length.should == SHA256Constants::DigestLength + end + + it "returns a SHA384 digest length" do + OpenSSL::Digest.new('sha384').digest_length.should == SHA384Constants::DigestLength + end + + it "returns a SHA512 digest length" do + OpenSSL::Digest.new('sha512').digest_length.should == SHA512Constants::DigestLength + end + end + + context "when the digest object is created via a subclass" do + it "returns a SHA1 digest length" do + OpenSSL::Digest::SHA1.new.digest_length.should == SHA1Constants::DigestLength + end + + it "returns a SHA256 digest length" do + OpenSSL::Digest::SHA256.new.digest_length.should == SHA256Constants::DigestLength + end + + it "returns a SHA384 digest length" do + OpenSSL::Digest::SHA384.new.digest_length.should == SHA384Constants::DigestLength + end + + it "returns a SHA512 digest length" do + OpenSSL::Digest::SHA512.new.digest_length.should == SHA512Constants::DigestLength + end + end +end diff --git a/spec/ruby/library/openssl/digest/digest_spec.rb b/spec/ruby/library/openssl/digest/digest_spec.rb new file mode 100644 index 0000000000..cf27d01b6d --- /dev/null +++ b/spec/ruby/library/openssl/digest/digest_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest class methods" do + describe ".digest" do + it "returns a SHA1 digest" do + OpenSSL::Digest.digest('sha1', SHA1Constants::Contents).should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest.digest('sha256', SHA256Constants::Contents).should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest.digest('sha384', SHA384Constants::Contents).should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest.digest('sha512', SHA512Constants::Contents).should == SHA512Constants::Digest + end + end + + describe ".hexdigest" do + it "returns a SHA1 hexdigest" do + OpenSSL::Digest.hexdigest('sha1', SHA1Constants::Contents).should == SHA1Constants::Hexdigest + end + + it "returns a SHA256 hexdigest" do + OpenSSL::Digest.hexdigest('sha256', SHA256Constants::Contents).should == SHA256Constants::Hexdigest + end + + it "returns a SHA384 hexdigest" do + OpenSSL::Digest.hexdigest('sha384', SHA384Constants::Contents).should == SHA384Constants::Hexdigest + end + + it "returns a SHA512 hexdigest" do + OpenSSL::Digest.hexdigest('sha512', SHA512Constants::Contents).should == SHA512Constants::Hexdigest + end + end + + describe ".base64digest" do + it "returns a SHA1 base64digest" do + OpenSSL::Digest.base64digest('sha1', SHA1Constants::Contents).should == SHA1Constants::Base64digest + end + + it "returns a SHA256 base64digest" do + OpenSSL::Digest.base64digest('sha256', SHA256Constants::Contents).should == SHA256Constants::Base64digest + end + + it "returns a SHA384 base64digest" do + OpenSSL::Digest.base64digest('sha384', SHA384Constants::Contents).should == SHA384Constants::Base64digest + end + + it "returns a SHA512 base64digest" do + OpenSSL::Digest.base64digest('sha512', SHA512Constants::Contents).should == SHA512Constants::Base64digest + end + end +end diff --git a/spec/ruby/library/openssl/digest/initialize_spec.rb b/spec/ruby/library/openssl/digest/initialize_spec.rb new file mode 100644 index 0000000000..b5911716ca --- /dev/null +++ b/spec/ruby/library/openssl/digest/initialize_spec.rb @@ -0,0 +1,137 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#initialize" do + describe "can be called with a digest name" do + it "returns a SHA1 object" do + OpenSSL::Digest.new("sha1").name.should == "SHA1" + end + + it "returns a SHA256 object" do + OpenSSL::Digest.new("sha256").name.should == "SHA256" + end + + it "returns a SHA384 object" do + OpenSSL::Digest.new("sha384").name.should == "SHA384" + end + + it "returns a SHA512 object" do + OpenSSL::Digest.new("sha512").name.should == "SHA512" + end + + version_is OpenSSL::VERSION, "4.0.0" do + it "throws an error when called with an unknown digest" do + -> { OpenSSL::Digest.new("wd40") }.should raise_error(OpenSSL::Digest::DigestError, /wd40/) + end + end + + it "cannot be called with a symbol" do + -> { OpenSSL::Digest.new(:SHA1) }.should raise_error(TypeError) + end + end + + describe "can be called with a digest object" do + it "returns a SHA1 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA1.new).name.should == "SHA1" + end + + it "returns a SHA256 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA256.new).name.should == "SHA256" + end + + it "returns a SHA384 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA384.new).name.should == "SHA384" + end + + it "returns a SHA512 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA512.new).name.should == "SHA512" + end + + it "ignores the state of the digest object" do + sha1 = OpenSSL::Digest.new('sha1', SHA1Constants::Contents) + OpenSSL::Digest.new(sha1).digest.should == SHA1Constants::BlankDigest + end + end + + it "cannot be called with a digest class" do + -> { OpenSSL::Digest.new(OpenSSL::Digest::SHA1) }.should raise_error(TypeError) + end + + context "when called without an initial String argument" do + it "returns a SHA1 digest" do + OpenSSL::Digest.new("sha1").digest.should == SHA1Constants::BlankDigest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest.new("sha256").digest.should == SHA256Constants::BlankDigest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest.new("sha384").digest.should == SHA384Constants::BlankDigest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest.new("sha512").digest.should == SHA512Constants::BlankDigest + end + end + + context "when called with an initial String argument" do + it "returns a SHA1 digest of that argument" do + OpenSSL::Digest.new("sha1", SHA1Constants::Contents).digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest of that argument" do + OpenSSL::Digest.new("sha256", SHA256Constants::Contents).digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest of that argument" do + OpenSSL::Digest.new("sha384", SHA384Constants::Contents).digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest of that argument" do + OpenSSL::Digest.new("sha512", SHA512Constants::Contents).digest.should == SHA512Constants::Digest + end + end + + context "can be called on subclasses" do + describe "can be called without an initial String argument on subclasses" do + it "returns a SHA1 digest" do + OpenSSL::Digest::SHA1.new.digest.should == SHA1Constants::BlankDigest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest::SHA256.new.digest.should == SHA256Constants::BlankDigest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest::SHA384.new.digest.should == SHA384Constants::BlankDigest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest::SHA512.new.digest.should == SHA512Constants::BlankDigest + end + end + + describe "can be called with an initial String argument on subclasses" do + it "returns a SHA1 digest" do + OpenSSL::Digest::SHA1.new(SHA1Constants::Contents).digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest::SHA256.new(SHA256Constants::Contents).digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest::SHA384.new(SHA384Constants::Contents).digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest::SHA512.new(SHA512Constants::Contents).digest.should == SHA512Constants::Digest + end + end + end +end diff --git a/spec/ruby/library/openssl/digest/name_spec.rb b/spec/ruby/library/openssl/digest/name_spec.rb new file mode 100644 index 0000000000..b379f35c1c --- /dev/null +++ b/spec/ruby/library/openssl/digest/name_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::Digest#name" do + it "returns the name of digest" do + OpenSSL::Digest.new('SHA1').name.should == 'SHA1' + end + + it "converts the name to the internal representation of OpenSSL" do + OpenSSL::Digest.new('sha1').name.should == 'SHA1' + end + + it "works on subclasses too" do + OpenSSL::Digest::SHA1.new.name.should == 'SHA1' + end +end diff --git a/spec/ruby/library/openssl/digest/reset_spec.rb b/spec/ruby/library/openssl/digest/reset_spec.rb new file mode 100644 index 0000000000..c19bf46633 --- /dev/null +++ b/spec/ruby/library/openssl/digest/reset_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#reset" do + it "works for a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1', SHA1Constants::Contents) + digest.reset + digest.update(SHA1Constants::Contents) + digest.digest.should == SHA1Constants::Digest + end + + it "works for a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256', SHA256Constants::Contents) + digest.reset + digest.update(SHA256Constants::Contents) + digest.digest.should == SHA256Constants::Digest + end + + it "works for a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384', SHA384Constants::Contents) + digest.reset + digest.update(SHA384Constants::Contents) + digest.digest.should == SHA384Constants::Digest + end + + it "works for a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512', SHA512Constants::Contents) + digest.reset + digest.update(SHA512Constants::Contents) + digest.digest.should == SHA512Constants::Digest + end +end diff --git a/spec/ruby/library/openssl/digest/shared/update.rb b/spec/ruby/library/openssl/digest/shared/update.rb new file mode 100644 index 0000000000..e5ff9dcb16 --- /dev/null +++ b/spec/ruby/library/openssl/digest/shared/update.rb @@ -0,0 +1,123 @@ +require_relative '../../../../library/digest/sha1/shared/constants' +require_relative '../../../../library/digest/sha256/shared/constants' +require_relative '../../../../library/digest/sha384/shared/constants' +require_relative '../../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe :openssl_digest_update, shared: true do + context "when given input as a single string" do + it "returns a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1') + digest.send(@method, SHA1Constants::Contents) + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256') + digest.send(@method, SHA256Constants::Contents) + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384') + digest.send(@method, SHA384Constants::Contents) + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512') + digest.send(@method, SHA512Constants::Contents) + digest.digest.should == SHA512Constants::Digest + end + end + + context "when given input as multiple smaller substrings" do + it "returns a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1') + SHA1Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256') + SHA256Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384') + SHA384Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512') + SHA512Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA512Constants::Digest + end + end + + context "when input is not a String and responds to #to_str" do + it "returns a SHA1 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA1Constants::Contents) + digest = OpenSSL::Digest.new('sha1') + digest.send(@method, str) + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA256Constants::Contents) + digest = OpenSSL::Digest.new('sha256') + digest.send(@method, str) + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA384Constants::Contents) + digest = OpenSSL::Digest.new('sha384') + digest.send(@method, str) + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA512Constants::Contents) + digest = OpenSSL::Digest.new('sha512') + digest.send(@method, str) + digest.digest.should == SHA512Constants::Digest + end + end + + context "when input is not a String and does not respond to #to_str" do + it "raises a TypeError with SHA1" do + digest = OpenSSL::Digest.new('sha1') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA256" do + digest = OpenSSL::Digest.new('sha256') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA384" do + digest = OpenSSL::Digest.new('sha384') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA512" do + digest = OpenSSL::Digest.new('sha512') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + end +end diff --git a/spec/ruby/library/openssl/digest/update_spec.rb b/spec/ruby/library/openssl/digest/update_spec.rb new file mode 100644 index 0000000000..3a90b06c6b --- /dev/null +++ b/spec/ruby/library/openssl/digest/update_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/update' + +describe "OpenSSL::Digest#update" do + it_behaves_like :openssl_digest_update, :update +end diff --git a/spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb b/spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb new file mode 100644 index 0000000000..5a2ca168b5 --- /dev/null +++ b/spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require 'openssl' + +describe "OpenSSL.fixed_length_secure_compare" do + it "returns true for two strings with the same content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the quick brown fox jumps over the lazy dog" + OpenSSL.fixed_length_secure_compare(input1, input2).should be_true + end + + it "returns false for two strings of equal size with different content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the lazy dog jumps over the quick brown fox" + OpenSSL.fixed_length_secure_compare(input1, input2).should be_false + end + + it "converts both arguments to strings using #to_str" do + input1 = mock("input1") + input1.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + input2 = mock("input2") + input2.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + OpenSSL.fixed_length_secure_compare(input1, input2).should be_true + end + + it "does not accept arguments that are not string and cannot be coerced into strings" do + -> { + OpenSSL.fixed_length_secure_compare("input1", :input2) + }.should raise_error(TypeError, 'no implicit conversion of Symbol into String') + + -> { + OpenSSL.fixed_length_secure_compare(Object.new, "input2") + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises an ArgumentError for two strings of different size" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the quick brown fox" + -> { + OpenSSL.fixed_length_secure_compare(input1, input2) + }.should raise_error(ArgumentError, 'inputs must be of equal length') + end +end diff --git a/spec/ruby/library/openssl/hmac/digest_spec.rb b/spec/ruby/library/openssl/hmac/digest_spec.rb new file mode 100644 index 0000000000..03ed136e64 --- /dev/null +++ b/spec/ruby/library/openssl/hmac/digest_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require_relative '../shared/constants' +require 'openssl' + +describe "OpenSSL::HMAC.digest" do + it "returns an SHA1 digest" do + cur_digest = OpenSSL::Digest.new("SHA1") + cur_digest.digest.should == HMACConstants::BlankSHA1Digest + digest = OpenSSL::HMAC.digest(cur_digest, + HMACConstants::Key, + HMACConstants::Contents) + digest.should == HMACConstants::SHA1Digest + end +end + +# Should add in similar specs for MD5, RIPEMD160, and SHA256 diff --git a/spec/ruby/library/openssl/hmac/hexdigest_spec.rb b/spec/ruby/library/openssl/hmac/hexdigest_spec.rb new file mode 100644 index 0000000000..3508c1bbd7 --- /dev/null +++ b/spec/ruby/library/openssl/hmac/hexdigest_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require_relative '../shared/constants' +require 'openssl' + +describe "OpenSSL::HMAC.hexdigest" do + it "returns an SHA1 hex digest" do + cur_digest = OpenSSL::Digest.new("SHA1") + cur_digest.hexdigest.should == HMACConstants::BlankSHA1HexDigest + hexdigest = OpenSSL::HMAC.hexdigest(cur_digest, + HMACConstants::Key, + HMACConstants::Contents) + hexdigest.should == HMACConstants::SHA1Hexdigest + end +end + +# Should add in similar specs for MD5, RIPEMD160, and SHA256 diff --git a/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb new file mode 100644 index 0000000000..1112972060 --- /dev/null +++ b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb @@ -0,0 +1,162 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::KDF.pbkdf2_hmac" do + before :each do + @defaults = { + salt: "\x00".b * 16, + iterations: 20_000, + length: 16, + hash: "sha1" + } + end + + it "creates the same value with the same input" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "supports nullbytes embedded in the password" do + key = OpenSSL::KDF.pbkdf2_hmac("sec\x00ret".b, **@defaults) + key.should == "\xB9\x7F\xB0\xC2\th\xC8<\x86\xF3\x94Ij7\xEF\xF1".b + end + + it "coerces the password into a String using #to_str" do + pass = mock("pass") + pass.should_receive(:to_str).and_return("secret") + key = OpenSSL::KDF.pbkdf2_hmac(pass, **@defaults) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the salt into a String using #to_str" do + salt = mock("salt") + salt.should_receive(:to_str).and_return("\x00".b * 16) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: salt) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the iterations into an Integer using #to_int" do + iterations = mock("iterations") + iterations.should_receive(:to_int).and_return(20_000) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: iterations) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the length into an Integer using #to_int" do + length = mock("length") + length.should_receive(:to_int).and_return(16) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: length) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "accepts a OpenSSL::Digest object as hash" do + hash = OpenSSL::Digest.new("sha1") + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "accepts an empty password" do + key = OpenSSL::KDF.pbkdf2_hmac("", **@defaults) + key.should == "k\x9F-\xB1\xF7\x9A\v\xA1(C\xF9\x85!P\xEF\x8C".b + end + + it "accepts an empty salt" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: "") + key.should == "\xD5f\xE5\xEA\xF91\x1D\xD3evD\xED\xDB\xE80\x80".b + end + + it "accepts an empty length" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 0) + key.should.empty? + end + + it "accepts an arbitrary length" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 19) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0\xCF\xBB\x7F".b + end + + it "accepts any hash function known to OpenSSL" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "sha512") + key.should == "N\x12}D\xCE\x99\xDBC\x8E\xEC\xAAr\xEA1\xDF\xFF".b + end + + it "raises a TypeError when password is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.pbkdf2_hmac(Object.new, **@defaults) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when salt is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when iterations is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: Object.new) + }.should raise_error(TypeError) + end + + version_is OpenSSL::VERSION, "4.0.0" do + it "raises a OpenSSL::Digest::DigestError for unknown digest algorithms" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "wd40") + }.should raise_error(OpenSSL::Digest::DigestError, /wd40/) + end + end + + it "treats salt as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:salt)) + }.should raise_error(ArgumentError, 'missing keyword: :salt') + end + + it "treats iterations as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:iterations)) + }.should raise_error(ArgumentError, 'missing keyword: :iterations') + end + + it "treats length as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:length)) + }.should raise_error(ArgumentError, 'missing keyword: :length') + end + + it "treats hash as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:hash)) + }.should raise_error(ArgumentError, 'missing keyword: :hash') + end + + it "treats all keywords as required" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret") + }.should raise_error(ArgumentError, 'missing keywords: :salt, :iterations, :length, :hash') + end + + guard -> { OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 } do + it "raises an OpenSSL::KDF::KDFError for 0 or less iterations" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 0) + }.should raise_error(OpenSSL::KDF::KDFError, "PKCS5_PBKDF2_HMAC: invalid iteration count") + + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /PKCS5_PBKDF2_HMAC/) + end + end +end diff --git a/spec/ruby/library/openssl/kdf/scrypt_spec.rb b/spec/ruby/library/openssl/kdf/scrypt_spec.rb new file mode 100644 index 0000000000..e01b8bca8a --- /dev/null +++ b/spec/ruby/library/openssl/kdf/scrypt_spec.rb @@ -0,0 +1,210 @@ +require_relative '../../../spec_helper' +require 'openssl' + +# LibreSSL seems not to support scrypt +guard -> { OpenSSL::OPENSSL_VERSION.start_with?('OpenSSL') and OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000 } do + describe "OpenSSL::KDF.scrypt" do + before :each do + @defaults = { + salt: "\x00".b * 16, + N: 2**14, + r: 8, + p: 1, + length: 32 + } + end + + it "creates the same value with the same input" do + key = OpenSSL::KDF.scrypt("secret", **@defaults) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "supports nullbytes embedded into the password" do + key = OpenSSL::KDF.scrypt("sec\x00ret".b, **@defaults) + key.should == "\xF9\xA4\xA0\xF1p\xF4\xF0\xCAT\xB4v\xEB\r7\x88N\xF7\x15]Ns\xFCwt4a\xC9\xC6\xA7\x13\x81&".b + end + + it "coerces the password into a String using #to_str" do + pass = mock("pass") + pass.should_receive(:to_str).and_return("secret") + key = OpenSSL::KDF.scrypt(pass, **@defaults) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the salt into a String using #to_str" do + salt = mock("salt") + salt.should_receive(:to_str).and_return("\x00".b * 16) + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: salt) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the N into an Integer using #to_int" do + n = mock("N") + n.should_receive(:to_int).and_return(2**14) + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: n) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the r into an Integer using #to_int" do + r = mock("r") + r.should_receive(:to_int).and_return(8) + key = OpenSSL::KDF.scrypt("secret", **@defaults, r: r) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the p into an Integer using #to_int" do + p = mock("p") + p.should_receive(:to_int).and_return(1) + key = OpenSSL::KDF.scrypt("secret", **@defaults, p: p) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the length into an Integer using #to_int" do + length = mock("length") + length.should_receive(:to_int).and_return(32) + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: length) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "accepts an empty password" do + key = OpenSSL::KDF.scrypt("", **@defaults) + key.should == "\xAA\xFC\xF5^E\x94v\xFFk\xE6\xF0vR\xE7\x13\xA7\xF5\x15'\x9A\xE4C\x9Dn\x18F_E\xD2\v\e\xB3".b + end + + it "accepts an empty salt" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: "") + key.should == "\x96\xACDl\xCB3/aN\xB0F\x8A#\xD7\x92\xD2O\x1E\v\xBB\xCE\xC0\xAA\xB9\x0F]\xB09\xEA8\xDD\e".b + end + + it "accepts a zero length" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 0) + key.should.empty? + end + + it "accepts an arbitrary length" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 19) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D".b + end + + it "raises a TypeError when password is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.scrypt(Object.new, **@defaults) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when salt is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, salt: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when N is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when r is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when p is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, length: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "treats salt as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:salt)) + }.should raise_error(ArgumentError, 'missing keyword: :salt') + end + + it "treats N as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:N)) + }.should raise_error(ArgumentError, 'missing keyword: :N') + end + + it "treats r as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:r)) + }.should raise_error(ArgumentError, 'missing keyword: :r') + end + + it "treats p as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:p)) + }.should raise_error(ArgumentError, 'missing keyword: :p') + end + + it "treats length as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:length)) + }.should raise_error(ArgumentError, 'missing keyword: :length') + end + + it "treats all keywords as required" do + -> { + OpenSSL::KDF.scrypt("secret") + }.should raise_error(ArgumentError, 'missing keywords: :salt, :N, :r, :p, :length') + end + + it "requires N to be a power of 2" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 2**14 - 1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires N to be at least 2" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: 2) + key.should == "\x06A$a\xA9!\xBE\x01\x85\xA7\x18\xBCEa\x82\xC5\xFEl\x93\xAB\xBD\xF7\x8B\x84\v\xFC\eN\xEBQ\xE6\xD2".b + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 0) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires r to be positive" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: 0) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires p to be positive" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: 0) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires length to be not negative" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, length: -1) + }.should raise_error(ArgumentError, "negative string size (or size too big)") + end + end +end diff --git a/spec/ruby/library/openssl/random/pseudo_bytes_spec.rb b/spec/ruby/library/openssl/random/pseudo_bytes_spec.rb new file mode 100644 index 0000000000..c5e450ce3d --- /dev/null +++ b/spec/ruby/library/openssl/random/pseudo_bytes_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../../spec_helper' +require_relative 'shared/random_bytes' + +if defined?(OpenSSL::Random.pseudo_bytes) + describe "OpenSSL::Random.pseudo_bytes" do + it_behaves_like :openssl_random_bytes, :pseudo_bytes + end +end diff --git a/spec/ruby/library/openssl/random/random_bytes_spec.rb b/spec/ruby/library/openssl/random/random_bytes_spec.rb new file mode 100644 index 0000000000..2678885da4 --- /dev/null +++ b/spec/ruby/library/openssl/random/random_bytes_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/random_bytes' + +describe "OpenSSL::Random.random_bytes" do + it_behaves_like :openssl_random_bytes, :random_bytes +end diff --git a/spec/ruby/library/openssl/random/shared/random_bytes.rb b/spec/ruby/library/openssl/random/shared/random_bytes.rb new file mode 100644 index 0000000000..f97ccd9974 --- /dev/null +++ b/spec/ruby/library/openssl/random/shared/random_bytes.rb @@ -0,0 +1,29 @@ +require_relative '../../../../spec_helper' +require 'openssl' + +describe :openssl_random_bytes, shared: true do + it "generates a random binary string of specified length" do + (1..64).each do |idx| + bytes = OpenSSL::Random.send(@method, idx) + bytes.should be_kind_of(String) + bytes.length.should == idx + end + end + + it "generates different binary strings with subsequent invocations" do + # quick and dirty check, but good enough + values = [] + 256.times do + val = OpenSSL::Random.send(@method, 16) + # make sure the random bytes are not repeating + values.include?(val).should == false + values << val + end + end + + it "raises ArgumentError on negative arguments" do + -> { + OpenSSL::Random.send(@method, -1) + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/openssl/secure_compare_spec.rb b/spec/ruby/library/openssl/secure_compare_spec.rb new file mode 100644 index 0000000000..cec48e01e7 --- /dev/null +++ b/spec/ruby/library/openssl/secure_compare_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require 'openssl' + +describe "OpenSSL.secure_compare" do + it "returns true for two strings with the same content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the quick brown fox jumps over the lazy dog" + OpenSSL.secure_compare(input1, input2).should be_true + end + + it "returns false for two strings with different content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the lazy dog jumps over the quick brown fox" + OpenSSL.secure_compare(input1, input2).should be_false + end + + it "converts both arguments to strings using #to_str, but adds equality check for the original objects" do + input1 = mock("input1") + input1.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + input2 = mock("input2") + input2.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + OpenSSL.secure_compare(input1, input2).should be_false + + input = mock("input") + input.should_receive(:to_str).twice.and_return("the quick brown fox jumps over the lazy dog") + OpenSSL.secure_compare(input, input).should be_true + end + + it "does not accept arguments that are not string and cannot be coerced into strings" do + -> { + OpenSSL.secure_compare("input1", :input2) + }.should raise_error(TypeError, 'no implicit conversion of Symbol into String') + + -> { + OpenSSL.secure_compare(Object.new, "input2") + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end +end diff --git a/spec/ruby/library/openssl/shared/constants.rb b/spec/ruby/library/openssl/shared/constants.rb new file mode 100644 index 0000000000..836f75011b --- /dev/null +++ b/spec/ruby/library/openssl/shared/constants.rb @@ -0,0 +1,11 @@ +# encoding: binary +module HMACConstants + + Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + Key = 'sekrit' + + BlankSHA1Digest = "\3329\243\356^kK\r2U\277\357\225`\030\220\257\330\a\t" + SHA1Digest = "\236\022\323\341\037\236\262n\344\t\372:\004J\242\330\257\270\363\264" + BlankSHA1HexDigest = "da39a3ee5e6b4b0d3255bfef95601890afd80709" + SHA1Hexdigest = "9e12d3e11f9eb26ee409fa3a044aa2d8afb8f3b4" +end diff --git a/spec/ruby/library/openssl/x509/name/parse_spec.rb b/spec/ruby/library/openssl/x509/name/parse_spec.rb new file mode 100644 index 0000000000..6624161d83 --- /dev/null +++ b/spec/ruby/library/openssl/x509/name/parse_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../../../spec_helper' +require 'openssl' + +describe "OpenSSL::X509::Name.parse" do + it "parses a /-delimited string of key-value pairs into a Name" do + dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" + name = OpenSSL::X509::Name.parse(dn) + + name.to_s.should == dn + + ary = name.to_a + + ary[0][0].should == "DC" + ary[1][0].should == "DC" + ary[2][0].should == "CN" + ary[0][1].should == "org" + ary[1][1].should == "ruby-lang" + ary[2][1].should == "www.ruby-lang.org" + ary[0][2].should == OpenSSL::ASN1::IA5STRING + ary[1][2].should == OpenSSL::ASN1::IA5STRING + ary[2][2].should == OpenSSL::ASN1::UTF8STRING + end + + it "parses a comma-delimited string of key-value pairs into a name" do + dn = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org" + name = OpenSSL::X509::Name.parse(dn) + + name.to_s.should == "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" + + ary = name.to_a + + ary[0][1].should == "org" + ary[1][1].should == "ruby-lang" + ary[2][1].should == "www.ruby-lang.org" + end + + it "raises TypeError if the given string contains no key/value pairs" do + -> do + OpenSSL::X509::Name.parse("hello") + end.should raise_error(TypeError) + end + + it "raises OpenSSL::X509::NameError if the given string contains invalid keys" do + -> do + OpenSSL::X509::Name.parse("hello=goodbye") + end.should raise_error(OpenSSL::X509::NameError) + end +end diff --git a/spec/ruby/library/openssl/x509/store/verify_spec.rb b/spec/ruby/library/openssl/x509/store/verify_spec.rb new file mode 100644 index 0000000000..6a6a53d992 --- /dev/null +++ b/spec/ruby/library/openssl/x509/store/verify_spec.rb @@ -0,0 +1,78 @@ +require_relative '../../../../spec_helper' +require 'openssl' + +describe "OpenSSL::X509::Store#verify" do + it "returns true for valid certificate" do + key = OpenSSL::PKey::RSA.new 2048 + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA" + cert.issuer = cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now - 10 + cert.not_after = cert.not_before + 365 * 24 * 60 * 60 + cert.sign key, OpenSSL::Digest.new('SHA256') + store = OpenSSL::X509::Store.new + store.add_cert(cert) + [store.verify(cert), store.error, store.error_string].should == [true, 0, "ok"] + end + + it "returns false for an expired certificate" do + key = OpenSSL::PKey::RSA.new 2048 + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA" + cert.issuer = cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now - 10 + cert.not_after = Time.now - 5 + cert.sign key, OpenSSL::Digest.new('SHA256') + store = OpenSSL::X509::Store.new + store.add_cert(cert) + store.verify(cert).should == false + end + + it "returns false for an expired root certificate" do + root_key = OpenSSL::PKey::RSA.new 2048 + root_cert = OpenSSL::X509::Certificate.new + root_cert.version = 2 + root_cert.serial = 1 + root_cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA" + root_cert.issuer = root_cert.subject + root_cert.public_key = root_key.public_key + root_cert.not_before = Time.now - 10 + root_cert.not_after = Time.now - 5 + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = root_cert + ef.issuer_certificate = root_cert + root_cert.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true)) + root_cert.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true)) + root_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false)) + root_cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false)) + root_cert.sign(root_key, OpenSSL::Digest.new('SHA256')) + + + key = OpenSSL::PKey::RSA.new 2048 + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 2 + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby certificate" + cert.issuer = root_cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now + cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = root_cert + cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true)) + cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false)) + cert.sign(root_key, OpenSSL::Digest.new('SHA256')) + + store = OpenSSL::X509::Store.new + store.add_cert(root_cert) + store.add_cert(cert) + store.verify(cert).should == false + end +end diff --git a/spec/ruby/library/openstruct/delete_field_spec.rb b/spec/ruby/library/openstruct/delete_field_spec.rb new file mode 100644 index 0000000000..9ac80196cc --- /dev/null +++ b/spec/ruby/library/openstruct/delete_field_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'ostruct' + +describe "OpenStruct#delete_field" do + before :each do + @os = OpenStruct.new(name: "John Smith", age: 70, pension: 300) + end + + it "removes the named field from self's method/value table" do + @os.delete_field(:name) + @os[:name].should be_nil + end + + it "does remove the accessor methods" do + @os.delete_field(:name) + @os.respond_to?(:name).should be_false + @os.respond_to?(:name=).should be_false + end +end diff --git a/spec/ruby/library/openstruct/element_reference_spec.rb b/spec/ruby/library/openstruct/element_reference_spec.rb new file mode 100644 index 0000000000..c425991b0f --- /dev/null +++ b/spec/ruby/library/openstruct/element_reference_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require "ostruct" + +describe "OpenStruct#[]" do + before :each do + @os = OpenStruct.new + end + + it "returns the associated value" do + @os.foo = 42 + @os[:foo].should == 42 + end +end diff --git a/spec/ruby/library/openstruct/element_set_spec.rb b/spec/ruby/library/openstruct/element_set_spec.rb new file mode 100644 index 0000000000..eeb5a8b318 --- /dev/null +++ b/spec/ruby/library/openstruct/element_set_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require "ostruct" + +describe "OpenStruct#[]=" do + before :each do + @os = OpenStruct.new + end + + it "sets the associated value" do + @os[:foo] = 42 + @os.foo.should == 42 + end +end diff --git a/spec/ruby/library/openstruct/equal_value_spec.rb b/spec/ruby/library/openstruct/equal_value_spec.rb new file mode 100644 index 0000000000..103ac13588 --- /dev/null +++ b/spec/ruby/library/openstruct/equal_value_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require "ostruct" +require_relative 'fixtures/classes' + +describe "OpenStruct#==" do + before :each do + @os = OpenStruct.new(name: "John") + end + + it "returns false when the passed argument is no OpenStruct" do + (@os == Object.new).should be_false + (@os == "Test").should be_false + (@os == 10).should be_false + (@os == :sym).should be_false + end + + it "returns true when self and other are equal method/value wise" do + (@os == @os).should be_true + (@os == OpenStruct.new(name: "John")).should be_true + (@os == OpenStructSpecs::OpenStructSub.new(name: "John")).should be_true + + (@os == OpenStruct.new(name: "Jonny")).should be_false + (@os == OpenStructSpecs::OpenStructSub.new(name: "Jonny")).should be_false + + (@os == OpenStruct.new(name: "John", age: 20)).should be_false + (@os == OpenStructSpecs::OpenStructSub.new(name: "John", age: 20)).should be_false + end +end diff --git a/spec/ruby/library/openstruct/fixtures/classes.rb b/spec/ruby/library/openstruct/fixtures/classes.rb new file mode 100644 index 0000000000..da42e8511d --- /dev/null +++ b/spec/ruby/library/openstruct/fixtures/classes.rb @@ -0,0 +1,4 @@ +module OpenStructSpecs + class OpenStructSub < OpenStruct + end +end diff --git a/spec/ruby/library/openstruct/frozen_spec.rb b/spec/ruby/library/openstruct/frozen_spec.rb new file mode 100644 index 0000000000..c14a4bac55 --- /dev/null +++ b/spec/ruby/library/openstruct/frozen_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require 'ostruct' + +describe "OpenStruct.new when frozen" do + before :each do + @os = OpenStruct.new(name: "John Smith", age: 70, pension: 300).freeze + end + # + # method_missing case handled in method_missing_spec.rb + # + it "is still readable" do + @os.age.should eql(70) + @os.pension.should eql(300) + @os.name.should == "John Smith" + end + + it "is not writable" do + ->{ @os.age = 42 }.should raise_error( RuntimeError ) + end + + it "cannot create new fields" do + ->{ @os.state = :new }.should raise_error( RuntimeError ) + end + + it "creates a frozen clone" do + f = @os.clone + f.frozen?.should == true + f.age.should == 70 + ->{ f.age = 0 }.should raise_error( RuntimeError ) + ->{ f.state = :newer }.should raise_error( RuntimeError ) + end + + it "creates an unfrozen dup" do + d = @os.dup + d.frozen?.should == false + d.age.should == 70 + d.age = 42 + d.age.should == 42 + end +end diff --git a/spec/ruby/library/openstruct/initialize_spec.rb b/spec/ruby/library/openstruct/initialize_spec.rb new file mode 100644 index 0000000000..dee5de48c6 --- /dev/null +++ b/spec/ruby/library/openstruct/initialize_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'ostruct' + +describe "OpenStruct#initialize" do + it "is private" do + OpenStruct.should have_private_instance_method(:initialize) + end +end diff --git a/spec/ruby/library/openstruct/inspect_spec.rb b/spec/ruby/library/openstruct/inspect_spec.rb new file mode 100644 index 0000000000..e2fed41528 --- /dev/null +++ b/spec/ruby/library/openstruct/inspect_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'ostruct' +require_relative 'fixtures/classes' +require_relative 'shared/inspect' + +describe "OpenStruct#inspect" do + it_behaves_like :ostruct_inspect, :inspect +end diff --git a/spec/ruby/library/openstruct/marshal_dump_spec.rb b/spec/ruby/library/openstruct/marshal_dump_spec.rb new file mode 100644 index 0000000000..5c38fd959e --- /dev/null +++ b/spec/ruby/library/openstruct/marshal_dump_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require "ostruct" + +describe "OpenStruct#marshal_dump" do + it "returns the method/value table" do + os = OpenStruct.new("age" => 20, "name" => "John") + os.marshal_dump.should == { age: 20, name: "John" } + end +end diff --git a/spec/ruby/library/openstruct/marshal_load_spec.rb b/spec/ruby/library/openstruct/marshal_load_spec.rb new file mode 100644 index 0000000000..342e5e68cd --- /dev/null +++ b/spec/ruby/library/openstruct/marshal_load_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require "ostruct" + +describe "OpenStruct#marshal_load when passed [Hash]" do + it "defines methods based on the passed Hash" do + os = OpenStruct.new + os.send :marshal_load, age: 20, name: "John" + + os.age.should eql(20) + os.name.should == "John" + end +end diff --git a/spec/ruby/library/openstruct/method_missing_spec.rb b/spec/ruby/library/openstruct/method_missing_spec.rb new file mode 100644 index 0000000000..89f83d07b3 --- /dev/null +++ b/spec/ruby/library/openstruct/method_missing_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require "ostruct" + +describe "OpenStruct#method_missing when called with a method name ending in '='" do + before :each do + @os = OpenStruct.new + end + + it "raises an ArgumentError when not passed any additional arguments" do + -> { @os.send(:test=) }.should raise_error(ArgumentError) + end +end + +describe "OpenStruct#method_missing when passed additional arguments" do + it "raises a NoMethodError when the key does not exist" do + os = OpenStruct.new + -> { os.test(1, 2, 3) }.should raise_error(NoMethodError) + end + + it "raises an ArgumentError when the key exists" do + os = OpenStruct.new(test: 20) + -> { os.test(1, 2, 3) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/openstruct/new_spec.rb b/spec/ruby/library/openstruct/new_spec.rb new file mode 100644 index 0000000000..5d2cacea40 --- /dev/null +++ b/spec/ruby/library/openstruct/new_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'ostruct' + +describe "OpenStruct.new when passed [Hash]" do + before :each do + @os = OpenStruct.new(name: "John Smith", age: 70, pension: 300) + end + + it "creates an attribute for each key of the passed Hash" do + @os.age.should eql(70) + @os.pension.should eql(300) + @os.name.should == "John Smith" + end +end + +describe "OpenStruct.new when passed no arguments" do + it "returns a new OpenStruct Object without any attributes" do + OpenStruct.new.to_s.should == "#<OpenStruct>" + end +end diff --git a/spec/ruby/library/openstruct/shared/inspect.rb b/spec/ruby/library/openstruct/shared/inspect.rb new file mode 100644 index 0000000000..ffcd690e1f --- /dev/null +++ b/spec/ruby/library/openstruct/shared/inspect.rb @@ -0,0 +1,20 @@ +describe :ostruct_inspect, shared: true do + it "returns a String representation of self" do + os = OpenStruct.new(name: "John Smith") + os.send(@method).should == "#<OpenStruct name=\"John Smith\">" + + os = OpenStruct.new(age: 20, name: "John Smith") + os.send(@method).should be_kind_of(String) + end + + it "correctly handles self-referential OpenStructs" do + os = OpenStruct.new + os.self = os + os.send(@method).should == "#<OpenStruct self=#<OpenStruct ...>>" + end + + it "correctly handles OpenStruct subclasses" do + os = OpenStructSpecs::OpenStructSub.new(name: "John Smith") + os.send(@method).should == "#<OpenStructSpecs::OpenStructSub name=\"John Smith\">" + end +end diff --git a/spec/ruby/library/openstruct/to_h_spec.rb b/spec/ruby/library/openstruct/to_h_spec.rb new file mode 100644 index 0000000000..6c272bcc71 --- /dev/null +++ b/spec/ruby/library/openstruct/to_h_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../spec_helper' +require 'ostruct' + +describe "OpenStruct#to_h" do + before :each do + @h = {name: "John Smith", age: 70, pension: 300} + @os = OpenStruct.new(@h) + @to_h = @os.to_h + end + + it "returns a Hash with members as keys" do + @to_h.should == @h + end + + it "returns a Hash with keys as symbols" do + os = OpenStruct.new("name" => "John Smith", "age" => 70) + os.pension = 300 + os.to_h.should == @h + end + + it "does not return the hash used as initializer" do + @to_h.should_not equal(@h) + end + + it "returns a Hash that is independent from the struct" do + @to_h[:age] = 71 + @os.age.should == 70 + end + + context "with block" do + it "converts [key, value] pairs returned by the block to a hash" do + h = @os.to_h { |k, v| [k.to_s, v*2] } + h.should == { "name" => "John SmithJohn Smith", "age" => 140, "pension" => 600 } + end + + it "raises ArgumentError if block returns longer or shorter array" do + -> do + @os.to_h { |k, v| [k.to_s, v*2, 1] } + end.should raise_error(ArgumentError, /element has wrong array length/) + + -> do + @os.to_h { |k, v| [k] } + end.should raise_error(ArgumentError, /element has wrong array length/) + end + + it "raises TypeError if block returns something other than Array" do + -> do + @os.to_h { |k, v| "not-array" } + end.should raise_error(TypeError, /wrong element type String/) + end + + it "coerces returned pair to Array with #to_ary" do + x = mock('x') + x.stub!(:to_ary).and_return([:b, 'b']) + + @os.to_h { |k| x }.should == { :b => 'b' } + end + + it "does not coerce returned pair to Array with #to_a" do + x = mock('x') + x.stub!(:to_a).and_return([:b, 'b']) + + -> do + @os.to_h { |k| x } + end.should raise_error(TypeError, /wrong element type MockObject/) + end + end +end diff --git a/spec/ruby/library/openstruct/to_s_spec.rb b/spec/ruby/library/openstruct/to_s_spec.rb new file mode 100644 index 0000000000..73d91bf981 --- /dev/null +++ b/spec/ruby/library/openstruct/to_s_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'ostruct' +require_relative 'fixtures/classes' +require_relative 'shared/inspect' + +describe "OpenStruct#to_s" do + it_behaves_like :ostruct_inspect, :to_s +end diff --git a/spec/ruby/library/optionparser/order_spec.rb b/spec/ruby/library/optionparser/order_spec.rb new file mode 100644 index 0000000000..e49bd25554 --- /dev/null +++ b/spec/ruby/library/optionparser/order_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'optparse' + +describe "OptionParser#order" do + it "accepts `into` keyword argument and stores result in it" do + options = {} + parser = OptionParser.new do |opts| + opts.on("-v", "--[no-]verbose", "Run verbosely") + opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script") + end + parser.order %w[--verbose --require optparse], into: options + + options.should == { verbose: true, require: "optparse" } + end +end + +describe "OptionParser#order!" do + it "accepts `into` keyword argument and stores result in it" do + options = {} + parser = OptionParser.new do |opts| + opts.on("-v", "--[no-]verbose", "Run verbosely") + opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script") + end + parser.order! %w[--verbose --require optparse], into: options + + options.should == { verbose: true, require: "optparse" } + end +end diff --git a/spec/ruby/library/optionparser/parse_spec.rb b/spec/ruby/library/optionparser/parse_spec.rb new file mode 100644 index 0000000000..9511acb1db --- /dev/null +++ b/spec/ruby/library/optionparser/parse_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'optparse' + +describe "OptionParser#parse" do + it "accepts `into` keyword argument and stores result in it" do + options = {} + parser = OptionParser.new do |opts| + opts.on("-v", "--[no-]verbose", "Run verbosely") + opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script") + end + parser.parse %w[--verbose --require optparse], into: options + + options.should == { verbose: true, require: "optparse" } + end +end + +describe "OptionParser#parse!" do + it "accepts `into` keyword argument and stores result in it" do + options = {} + parser = OptionParser.new do |opts| + opts.on("-v", "--[no-]verbose", "Run verbosely") + opts.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script") + end + parser.parse! %w[--verbose --require optparse], into: options + + options.should == { verbose: true, require: "optparse" } + end +end diff --git a/spec/ruby/library/pathname/absolute_spec.rb b/spec/ruby/library/pathname/absolute_spec.rb new file mode 100644 index 0000000000..109abb8ee9 --- /dev/null +++ b/spec/ruby/library/pathname/absolute_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#absolute?" do + + it "returns true for the root directory" do + Pathname.new('/').should.absolute? + end + + it "returns true for a dir starting with a slash" do + Pathname.new('/usr/local/bin').should.absolute? + end + + it "returns false for a dir not starting with a slash" do + Pathname.new('fish').should_not.absolute? + end + + it "returns false for a dir not starting with a slash" do + Pathname.new('fish/dog/cow').should_not.absolute? + end + +end diff --git a/spec/ruby/library/pathname/birthtime_spec.rb b/spec/ruby/library/pathname/birthtime_spec.rb new file mode 100644 index 0000000000..109c112303 --- /dev/null +++ b/spec/ruby/library/pathname/birthtime_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#birthtime" do + platform_is :windows, :darwin, :freebsd, :netbsd do + it "returns the birth time for self" do + Pathname.new(__FILE__).birthtime.should be_kind_of(Time) + end + end + + platform_is :openbsd do + it "raises an NotImplementedError" do + -> { Pathname.new(__FILE__).birthtime }.should raise_error(NotImplementedError) + end + end +end diff --git a/spec/ruby/library/pathname/divide_spec.rb b/spec/ruby/library/pathname/divide_spec.rb new file mode 100644 index 0000000000..8af79d0c8f --- /dev/null +++ b/spec/ruby/library/pathname/divide_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/plus' + +describe "Pathname#/" do + it_behaves_like :pathname_plus, :/ +end diff --git a/spec/ruby/library/pathname/empty_spec.rb b/spec/ruby/library/pathname/empty_spec.rb new file mode 100644 index 0000000000..4deade5b64 --- /dev/null +++ b/spec/ruby/library/pathname/empty_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe 'Pathname#empty?' do + before :all do + @file = tmp 'new_file_path_name.txt' + touch @file + @dir = tmp 'new_directory_path_name' + Dir.mkdir @dir + end + + after :all do + rm_r @file + rm_r @dir + end + + it 'returns true when file is not empty' do + Pathname.new(__FILE__).empty?.should be_false + end + + it 'returns false when the directory is not empty' do + Pathname.new(__dir__).empty?.should be_false + end + + it 'return true when file is empty' do + Pathname.new(@file).empty?.should be_true + end + + it 'returns true when directory is empty' do + Pathname.new(@dir).empty?.should be_true + end +end diff --git a/spec/ruby/library/pathname/equal_value_spec.rb b/spec/ruby/library/pathname/equal_value_spec.rb new file mode 100644 index 0000000000..92d4767e76 --- /dev/null +++ b/spec/ruby/library/pathname/equal_value_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#==" do + + it "returns true when identical paths are used" do + (Pathname.new('') == Pathname.new('')).should == true + end + + it "returns true when identical paths are used" do + (Pathname.new('') == Pathname.new('/usr/local/bin')).should == false + end + +end diff --git a/spec/ruby/library/pathname/glob_spec.rb b/spec/ruby/library/pathname/glob_spec.rb new file mode 100644 index 0000000000..de322bab47 --- /dev/null +++ b/spec/ruby/library/pathname/glob_spec.rb @@ -0,0 +1,92 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe 'Pathname.glob' do + before :all do + @dir = tmp('pathname_glob') + '/' + @file_1 = @dir + 'lib/ipaddr.rb' + @file_2 = @dir + 'lib/irb.rb' + @file_3 = @dir + 'lib/.hidden.rb' + + touch @file_1 + touch @file_2 + touch @file_3 + end + + after :all do + rm_r @dir[0...-1] + end + + it 'returns [] for no match' do + Pathname.glob(@dir + 'lib/*.js').should == [] + end + + it 'returns [] when the pathname does not exist' do + Pathname.glob('i_dont_exist/lib/*.js').should == [] + end + + it 'returns matching file paths' do + Pathname.glob(@dir + 'lib/*i*.rb').sort.should == [Pathname.new(@file_1), Pathname.new(@file_2)].sort + end + + it 'returns matching file paths when a flag is provided' do + expected = [Pathname.new(@file_1), Pathname.new(@file_2), Pathname.new(@file_3)].sort + Pathname.glob(@dir + 'lib/*i*.rb', File::FNM_DOTMATCH).sort.should == expected + end + + it 'returns matching file paths when supplied :base keyword argument' do + Pathname.glob('*i*.rb', base: @dir + 'lib').sort.should == [Pathname.new('ipaddr.rb'), Pathname.new('irb.rb')].sort + end + + it "raises an ArgumentError when supplied a keyword argument other than :base" do + -> { + Pathname.glob('*i*.rb', foo: @dir + 'lib') + }.should raise_error(ArgumentError, /unknown keyword: :?foo/) + end + + it "does not raise an ArgumentError when supplied a flag and :base keyword argument" do + expected = [Pathname.new('ipaddr.rb'), Pathname.new('irb.rb'), Pathname.new('.hidden.rb')].sort + Pathname.glob('*i*.rb', File::FNM_DOTMATCH, base: @dir + 'lib').sort.should == expected + end +end + + +describe 'Pathname#glob' do + before :all do + @dir = tmp('pathname_glob') + '/' + @file_1 = @dir + 'lib/ipaddr.rb' + @file_2 = @dir + 'lib/irb.rb' + @file_3 = @dir + 'lib/.hidden.rb' + + touch @file_1 + touch @file_2 + touch @file_3 + end + + after :all do + rm_r @dir[0...-1] + end + + it 'returns [] for no match' do + Pathname.new(@dir).glob('lib/*.js').should == [] + end + + it 'returns [] when the pathname does not exist' do + Pathname.new('./i_dont_exist').glob('lib/*.js').should == [] + end + + it 'returns matching file paths' do + Pathname.new(@dir).glob('lib/*i*.rb').sort.should == [Pathname.new(@file_1), Pathname.new(@file_2)].sort + end + + it 'yields matching file paths to block' do + ary = [] + Pathname.new(@dir).glob('lib/*i*.rb') { |p| ary << p }.should be_nil + ary.sort.should == [Pathname.new(@file_1), Pathname.new(@file_2)].sort + end + + it 'returns matching file paths when a flag is provided' do + expected = [Pathname.new(@file_1), Pathname.new(@file_2), Pathname.new(@file_3)].sort + Pathname.new(@dir).glob('lib/*i*.rb', File::FNM_DOTMATCH).sort.should == expected + end +end diff --git a/spec/ruby/library/pathname/hash_spec.rb b/spec/ruby/library/pathname/hash_spec.rb new file mode 100644 index 0000000000..da1b8f4f76 --- /dev/null +++ b/spec/ruby/library/pathname/hash_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#hash" do + + it "is equal to the hash of the pathname" do + Pathname.new('/usr/local/bin/').hash.should == '/usr/local/bin/'.hash + end + + it "is not equal the hash of a different pathname" do + Pathname.new('/usr/local/bin/').hash.should_not == '/usr/bin/'.hash + end + +end diff --git a/spec/ruby/library/pathname/inspect_spec.rb b/spec/ruby/library/pathname/inspect_spec.rb new file mode 100644 index 0000000000..304746fbe5 --- /dev/null +++ b/spec/ruby/library/pathname/inspect_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#inspect" do + it "returns a consistent String" do + result = Pathname.new('/tmp').inspect + result.should be_an_instance_of(String) + result.should == "#<Pathname:/tmp>" + end +end diff --git a/spec/ruby/library/pathname/join_spec.rb b/spec/ruby/library/pathname/join_spec.rb new file mode 100644 index 0000000000..a0877777fc --- /dev/null +++ b/spec/ruby/library/pathname/join_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#join" do + it "without separators" do + Pathname.new('/usr').join(Pathname.new('foo')).should == Pathname.new('/usr/foo') + end + + it "with separators" do + Pathname.new('/usr').join(Pathname.new('/foo')).should == Pathname.new('/foo') + end + + it "with a string" do + Pathname.new('/usr').join('foo').should == Pathname.new('/usr/foo') + end + + it "with root" do + Pathname.new('/usr').join(Pathname.new('/')).should == Pathname.new('/') + end + + it "with a relative path" do + Pathname.new('/usr').join(Pathname.new('../foo')).should == Pathname.new('/foo') + end + + it "a relative path with current" do + Pathname.new('.').join(Pathname.new('foo')).should == Pathname.new('foo') + end + + it "an absolute path with current" do + Pathname.new('.').join(Pathname.new('/foo')).should == Pathname.new('/foo') + end + + it "a prefixed relative path with current" do + Pathname.new('.').join(Pathname.new('./foo')).should == Pathname.new('foo') + end + + it "multiple paths" do + Pathname.new('.').join(Pathname.new('./foo'), 'bar').should == Pathname.new('foo/bar') + end +end diff --git a/spec/ruby/library/pathname/new_spec.rb b/spec/ruby/library/pathname/new_spec.rb new file mode 100644 index 0000000000..36226ed515 --- /dev/null +++ b/spec/ruby/library/pathname/new_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname.new" do + it "returns a new Pathname Object with 1 argument" do + Pathname.new('').should be_kind_of(Pathname) + end + + it "raises an ArgumentError when called with \0" do + -> { Pathname.new("\0")}.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + -> { Pathname.new(nil) }.should raise_error(TypeError) + -> { Pathname.new(0) }.should raise_error(TypeError) + -> { Pathname.new(true) }.should raise_error(TypeError) + -> { Pathname.new(false) }.should raise_error(TypeError) + end + + it "initializes with an object with to_path" do + Pathname.new(mock_to_path('foo')).should == Pathname.new('foo') + end +end diff --git a/spec/ruby/library/pathname/parent_spec.rb b/spec/ruby/library/pathname/parent_spec.rb new file mode 100644 index 0000000000..3843bb22ed --- /dev/null +++ b/spec/ruby/library/pathname/parent_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#parent" do + + it "has parent of root as root" do + Pathname.new('/').parent.to_s.should == '/' + end + + it "has parent of /usr/ as root" do + Pathname.new('/usr/').parent.to_s.should == '/' + end + + it "has parent of /usr/local as root" do + Pathname.new('/usr/local').parent.to_s.should == '/usr' + end + +end diff --git a/spec/ruby/library/pathname/pathname_spec.rb b/spec/ruby/library/pathname/pathname_spec.rb new file mode 100644 index 0000000000..0fb2881468 --- /dev/null +++ b/spec/ruby/library/pathname/pathname_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Kernel#Pathname" do + it "is a private instance method" do + Kernel.should have_private_instance_method(:Pathname) + end + + it "is also a public method" do + Kernel.should have_method(:Pathname) + end + + it "returns same argument when called with a pathname argument" do + path = Pathname('foo') + new_path = Pathname(path) + + path.should.equal?(new_path) + end +end diff --git a/spec/ruby/library/pathname/plus_spec.rb b/spec/ruby/library/pathname/plus_spec.rb new file mode 100644 index 0000000000..57e472c266 --- /dev/null +++ b/spec/ruby/library/pathname/plus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/plus' + +describe "Pathname#+" do + it_behaves_like :pathname_plus, :+ +end diff --git a/spec/ruby/library/pathname/realdirpath_spec.rb b/spec/ruby/library/pathname/realdirpath_spec.rb new file mode 100644 index 0000000000..a9e44e354e --- /dev/null +++ b/spec/ruby/library/pathname/realdirpath_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#realdirpath" do + + it "returns a Pathname" do + Pathname.pwd.realdirpath.should be_an_instance_of(Pathname) + end + +end diff --git a/spec/ruby/library/pathname/realpath_spec.rb b/spec/ruby/library/pathname/realpath_spec.rb new file mode 100644 index 0000000000..f2c654308e --- /dev/null +++ b/spec/ruby/library/pathname/realpath_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#realpath" do + + it "returns a Pathname" do + Pathname.pwd.realpath.should be_an_instance_of(Pathname) + end + +end diff --git a/spec/ruby/library/pathname/relative_path_from_spec.rb b/spec/ruby/library/pathname/relative_path_from_spec.rb new file mode 100644 index 0000000000..133a149849 --- /dev/null +++ b/spec/ruby/library/pathname/relative_path_from_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#relative_path_from" do + def relative_path_str(dest, base) + Pathname.new(dest).relative_path_from(Pathname.new(base)).to_s + end + + it "raises an error when the two paths do not share a common prefix" do + -> { relative_path_str('/usr', 'foo') }.should raise_error(ArgumentError) + end + + it "raises an error when the base directory has .." do + -> { relative_path_str('a', '..') }.should raise_error(ArgumentError) + end + + it "returns a path relative from root" do + relative_path_str('/usr', '/').should == 'usr' + end + + it 'returns 1 level up when both paths are relative' do + relative_path_str('a', 'b').should == '../a' + relative_path_str('a', 'b/').should == '../a' + end + + it 'returns a relative path when both are absolute' do + relative_path_str('/a', '/b').should == '../a' + end + + it "returns a path relative to the current directory" do + relative_path_str('/usr/bin/ls', '/usr').should == 'bin/ls' + end + + it 'returns a . when base and dest are the same' do + relative_path_str('/usr', '/usr').should == '.' + end + + it 'returns the same directory with a non clean base that matches the current dir' do + relative_path_str('/usr', '/stuff/..').should == 'usr' + end + + it 'returns a relative path with a non clean base that matches a different dir' do + relative_path_str('/usr', '/stuff/../foo').should == '../usr' + end + + it 'returns current and pattern when only those patterns are used' do + relative_path_str('.', '.').should == '.' + relative_path_str('..', '..').should == '.' + relative_path_str('..', '.').should == '..' + end + + it 'converts string argument to Pathname' do + Pathname.new('/usr/bin/ls').relative_path_from('/usr').to_s.should == 'bin/ls' + end +end diff --git a/spec/ruby/library/pathname/relative_spec.rb b/spec/ruby/library/pathname/relative_spec.rb new file mode 100644 index 0000000000..0fab9a7b9f --- /dev/null +++ b/spec/ruby/library/pathname/relative_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#relative?" do + + it "returns false for the root directory" do + Pathname.new('/').should_not.relative? + end + + it "returns false for a dir starting with a slash" do + Pathname.new('/usr/local/bin').should_not.relative? + end + + it "returns true for a dir not starting with a slash" do + Pathname.new('fish').should.relative? + end + + it "returns true for a dir not starting with a slash" do + Pathname.new('fish/dog/cow').should.relative? + end + +end diff --git a/spec/ruby/library/pathname/root_spec.rb b/spec/ruby/library/pathname/root_spec.rb new file mode 100644 index 0000000000..cd2be24516 --- /dev/null +++ b/spec/ruby/library/pathname/root_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#root?" do + + it "returns true for root directories" do + Pathname.new('/').should.root? + end + + it "returns false for empty string" do + Pathname.new('').should_not.root? + end + + it "returns false for a top level directory" do + Pathname.new('/usr').should_not.root? + end + + it "returns false for a top level with .. appended directory" do + Pathname.new('/usr/..').should_not.root? + end + + it "returns false for a directory below top level" do + Pathname.new('/usr/local/bin/').should_not.root? + end + +end diff --git a/spec/ruby/library/pathname/shared/plus.rb b/spec/ruby/library/pathname/shared/plus.rb new file mode 100644 index 0000000000..b3b896ea43 --- /dev/null +++ b/spec/ruby/library/pathname/shared/plus.rb @@ -0,0 +1,8 @@ +require 'pathname' + +describe :pathname_plus, shared: true do + it "appends a pathname to self" do + p = Pathname.new("/usr") + p.send(@method, "bin/ruby").should == Pathname.new("/usr/bin/ruby") + end +end diff --git a/spec/ruby/library/pathname/sub_spec.rb b/spec/ruby/library/pathname/sub_spec.rb new file mode 100644 index 0000000000..ad2900f62b --- /dev/null +++ b/spec/ruby/library/pathname/sub_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#sub" do + + it "replaces the pattern with rest" do + Pathname.new('/usr/local/bin/').sub(/local/, 'fish').to_s.should == '/usr/fish/bin/' + end + + it "returns a new object" do + p = Pathname.new('/usr/local/bin/') + p.sub(/local/, 'fish').should_not == p + end + +end diff --git a/spec/ruby/library/pp/pp_spec.rb b/spec/ruby/library/pp/pp_spec.rb new file mode 100644 index 0000000000..e45a6bb94f --- /dev/null +++ b/spec/ruby/library/pp/pp_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'pp' + +describe "PP.pp" do + it 'works with default arguments' do + array = [1, 2, 3] + + -> { + PP.pp array + }.should output "[1, 2, 3]\n" + end + + it 'allows specifying out explicitly' do + array = [1, 2, 3] + other_out = IOStub.new + + -> { + PP.pp array, other_out + }.should output "" # no output on stdout + + other_out.to_s.should == "[1, 2, 3]\n" + end + + it 'correctly prints a Hash' do + hash = { 'key' => 42 } + -> { + PP.pp hash + }.should output("#{hash.inspect}\n") + end +end diff --git a/spec/ruby/library/prime/each_spec.rb b/spec/ruby/library/prime/each_spec.rb new file mode 100644 index 0000000000..b99cf7cf0e --- /dev/null +++ b/spec/ruby/library/prime/each_spec.rb @@ -0,0 +1,167 @@ +require_relative '../../spec_helper' +require 'prime' + +describe :prime_each, shared: true do + before :each do + ScratchPad.record [] + end + + it "enumerates primes" do + primes = Prime.instance + result = [] + + primes.each { |p| + result << p + break if p > 10 + } + + result.should == [2, 3, 5, 7, 11] + end + + it "yields ascending primes to the block" do + previous = 1 + @object.each do |prime| + break if prime > 1000 + ScratchPad << prime + prime.should > previous + previous = prime + end + + all_prime = true + ScratchPad.recorded.all? do |prime| + all_prime &&= (2..Math.sqrt(prime)).all? { |d| prime % d != 0 } + end + + all_prime.should be_true + end + + it "returns the last evaluated expression in the passed block" do + @object.each { break :value }.should equal(:value) + end + + describe "when not passed a block" do + before :each do + @prime_enum = @object.each + end + + it "returns an object that is Enumerable" do + @prime_enum.each.should be_kind_of(Enumerable) + end + + it "returns an object that responds to #with_index" do + @prime_enum.should respond_to(:with_index) + end + + it "returns an object that responds to #with_object" do + @prime_enum.should respond_to(:with_object) + end + + it "returns an object that responds to #next" do + @prime_enum.should respond_to(:next) + end + + it "returns an object that responds to #rewind" do + @prime_enum.should respond_to(:rewind) + end + + it "yields primes starting at 2 independent of prior enumerators" do + @prime_enum.next.should == 2 + @prime_enum.next.should == 3 + + @object.each { |prime| break prime }.should == 2 + end + + it "returns an enumerator that yields previous primes when #rewind is called" do + @prime_enum.next.should == 2 + @prime_enum.next.should == 3 + @prime_enum.rewind + @prime_enum.next.should == 2 + end + + it "returns independent enumerators" do + enum = @object.each + enum.next.should == 2 + enum.next.should == 3 + + @prime_enum.next.should == 2 + + enum.next.should == 5 + end + end +end + +describe :prime_each_with_arguments, shared: true do + before :each do + ScratchPad.record [] + end + + it "yields ascending primes less than or equal to the argument" do + bound = 1000 + previous = 1 + @object.each(bound) do |prime| + ScratchPad << prime + prime.should > previous + previous = prime + end + + ScratchPad.recorded.all? do |prime| + (2..Math.sqrt(prime)).all? { |d| prime % d != 0 } + end.should be_true + + ScratchPad.recorded.all? { |prime| prime <= bound }.should be_true + end + + it "returns nil when no prime is generated" do + @object.each(1) { :value }.should be_nil + end + + it "yields primes starting at 2 independent of prior enumeration" do + @object.each(10) { |prime| prime }.should == 7 + @object.each(10) { |prime| break prime }.should == 2 + end + + it "accepts a pseudo-prime generator as the second argument" do + generator = mock('very bad pseudo-prime generator') + generator.should_receive(:upper_bound=).with(100) + generator.should_receive(:each).and_yield(2).and_yield(3).and_yield(4) + + @object.each(100, generator) { |prime| ScratchPad << prime } + ScratchPad.recorded.should == [2, 3, 4] + end + + describe "when not passed a block" do + it "returns an object that returns primes less than or equal to the bound" do + bound = 100 + @object.each(bound).all? { |prime| prime <= bound }.should be_true + end + end +end + +describe "Prime.each" do + it_behaves_like :prime_each, :each, Prime +end + +describe "Prime.each" do + it_behaves_like :prime_each_with_arguments, :each, Prime +end + +describe "Prime#each with Prime.instance" do + it_behaves_like :prime_each, :each, Prime.instance +end + +describe "Prime#each with Prime.instance" do + it_behaves_like :prime_each_with_arguments, :each, Prime.instance +end + +describe "Prime#each with Prime.instance" do + before :each do + @object = Prime.instance + end + + it_behaves_like :prime_each, :each + + it "resets the enumerator with each call" do + @object.each { |prime| break if prime > 10 } + @object.each { |prime| break prime }.should == 2 + end +end diff --git a/spec/ruby/library/prime/instance_spec.rb b/spec/ruby/library/prime/instance_spec.rb new file mode 100644 index 0000000000..5183f36901 --- /dev/null +++ b/spec/ruby/library/prime/instance_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require 'prime' + +describe "Prime.instance" do + it "returns a object representing the set of prime numbers" do + Prime.instance.should be_kind_of(Prime) + end + + it "returns a object with no obsolete features" do + Prime.instance.should_not respond_to(:succ) + Prime.instance.should_not respond_to(:next) + end + + it "does not complain anything" do + -> { Prime.instance }.should_not complain + end + + it "raises a ArgumentError when is called with some arguments" do + -> { Prime.instance(1) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/prime/int_from_prime_division_spec.rb b/spec/ruby/library/prime/int_from_prime_division_spec.rb new file mode 100644 index 0000000000..5abb7221df --- /dev/null +++ b/spec/ruby/library/prime/int_from_prime_division_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'prime' + +describe "Prime.int_from_prime_division" do + it "returns the product of the given factorization" do + Prime.int_from_prime_division([[2,3], [3,3], [5,3], [7,3], [11,3], [13,3], [17,3]]). + should == 2**3 * 3**3 * 5**3 * 7**3 * 11**3 * 13**3 * 17**3 + end + + it "returns 1 for an empty factorization" do + Prime.int_from_prime_division([]).should == 1 + end +end diff --git a/spec/ruby/library/prime/integer/each_prime_spec.rb b/spec/ruby/library/prime/integer/each_prime_spec.rb new file mode 100644 index 0000000000..a71296b0df --- /dev/null +++ b/spec/ruby/library/prime/integer/each_prime_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require 'prime' + +describe "Integer.each_prime" do + it "is transferred to Prime.each" do + Prime.should_receive(:each).with(100).and_yield(2).and_yield(3).and_yield(5) + yielded = [] + Integer.each_prime(100) do |prime| + yielded << prime + end + yielded.should == [2,3,5] + end +end diff --git a/spec/ruby/library/prime/integer/from_prime_division_spec.rb b/spec/ruby/library/prime/integer/from_prime_division_spec.rb new file mode 100644 index 0000000000..e0e74fb336 --- /dev/null +++ b/spec/ruby/library/prime/integer/from_prime_division_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require 'prime' + +describe "Integer.from_prime_division" do + it "returns the product of the given factorization" do + Integer.from_prime_division([[2,3], [3,3], [5,3], [7,3], [11,3], [13,3], [17,3]]). + should == 2**3 * 3**3 * 5**3 * 7**3 * 11**3 * 13**3 * 17**3 + end + + it "returns 1 for an empty factorization" do + Integer.from_prime_division([]).should == 1 + end +end diff --git a/spec/ruby/library/prime/integer/prime_division_spec.rb b/spec/ruby/library/prime/integer/prime_division_spec.rb new file mode 100644 index 0000000000..be03438a6f --- /dev/null +++ b/spec/ruby/library/prime/integer/prime_division_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../spec_helper' +require 'prime' + +describe "Integer#prime_division" do + it "returns an array of a prime factor and a corresponding exponent" do + (2*3*5*7*11*13*17).prime_division.should == + [[2,1], [3,1], [5,1], [7,1], [11,1], [13,1], [17,1]] + end + + it "returns an empty array for 1" do + 1.prime_division.should == [] + end + it "returns an empty array for -1" do + -1.prime_division.should == [[-1, 1]] + end + it "raises ZeroDivisionError for 0" do + -> { 0.prime_division }.should raise_error(ZeroDivisionError) + end +end diff --git a/spec/ruby/library/prime/integer/prime_spec.rb b/spec/ruby/library/prime/integer/prime_spec.rb new file mode 100644 index 0000000000..53de76d5ab --- /dev/null +++ b/spec/ruby/library/prime/integer/prime_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' +require 'prime' + +describe "Integer#prime?" do + it "returns a true value for prime numbers" do + 2.prime?.should be_true + 3.prime?.should be_true + (2**31-1).prime?.should be_true # 8th Mersenne prime (M8) + end + + it "returns a false value for composite numbers" do + 4.prime?.should be_false + 15.prime?.should be_false + (2**32-1).prime?.should be_false + ( (2**17-1)*(2**19-1) ).prime?.should be_false # M6*M7 + end +end diff --git a/spec/ruby/library/prime/next_spec.rb b/spec/ruby/library/prime/next_spec.rb new file mode 100644 index 0000000000..39c4ae16ae --- /dev/null +++ b/spec/ruby/library/prime/next_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/next' +require 'prime' + +describe "Prime#next" do + it_behaves_like :prime_next, :next +end diff --git a/spec/ruby/library/prime/prime_division_spec.rb b/spec/ruby/library/prime/prime_division_spec.rb new file mode 100644 index 0000000000..6293478f59 --- /dev/null +++ b/spec/ruby/library/prime/prime_division_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require 'prime' + +describe "Prime.prime_division" do + it "returns an array of a prime factor and a corresponding exponent" do + Prime.prime_division(2*3*5*7*11*13*17).should == + [[2,1], [3,1], [5,1], [7,1], [11,1], [13,1], [17,1]] + end + + it "returns an empty array for 1" do + Prime.prime_division(1).should == [] + end + + it "returns [[-1, 1]] for -1" do + Prime.prime_division(-1).should == [[-1, 1]] + end + + it "includes [[-1, 1]] in the divisors of a negative number" do + Prime.prime_division(-10).should include([-1, 1]) + end + + it "raises ZeroDivisionError for 0" do + -> { Prime.prime_division(0) }.should raise_error(ZeroDivisionError) + end +end diff --git a/spec/ruby/library/prime/prime_spec.rb b/spec/ruby/library/prime/prime_spec.rb new file mode 100644 index 0000000000..0896c7f0f3 --- /dev/null +++ b/spec/ruby/library/prime/prime_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'prime' + +describe "Prime#prime?" do + it "returns a true value for prime numbers" do + Prime.prime?(2).should be_true + Prime.prime?(3).should be_true + Prime.prime?(2**31-1).should be_true # 8th Mersenne prime (M8) + end + + it "returns a false value for composite numbers" do + Prime.prime?(4).should be_false + Prime.prime?(15).should be_false + Prime.prime?(2**32-1).should be_false + Prime.prime?( (2**17-1)*(2**19-1) ).should be_false # M6*M7 + end +end diff --git a/spec/ruby/library/prime/shared/next.rb b/spec/ruby/library/prime/shared/next.rb new file mode 100644 index 0000000000..f79b2c051e --- /dev/null +++ b/spec/ruby/library/prime/shared/next.rb @@ -0,0 +1,8 @@ +describe :prime_next, shared: true do + it "returns the element at the current position and moves forward" do + p = Prime.instance.each + p.next.should == 2 + p.next.should == 3 + p.next.next.should == 6 + end +end diff --git a/spec/ruby/library/prime/succ_spec.rb b/spec/ruby/library/prime/succ_spec.rb new file mode 100644 index 0000000000..34c18d2ba0 --- /dev/null +++ b/spec/ruby/library/prime/succ_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/next' +require 'prime' + +describe "Prime#succ" do + it_behaves_like :prime_next, :succ +end diff --git a/spec/ruby/library/random/formatter/alphanumeric_spec.rb b/spec/ruby/library/random/formatter/alphanumeric_spec.rb new file mode 100644 index 0000000000..9bd325e1d0 --- /dev/null +++ b/spec/ruby/library/random/formatter/alphanumeric_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../../spec_helper' + +require 'random/formatter' + +describe "Random::Formatter#alphanumeric" do + before :each do + @object = Object.new + @object.extend(Random::Formatter) + @object.define_singleton_method(:bytes) do |n| + "\x00".b * n + end + end + + it "generates a random alphanumeric string" do + @object.alphanumeric.should =~ /\A[A-Za-z0-9]+\z/ + end + + it "has a default size of 16 characters" do + @object.alphanumeric.size.should == 16 + end + + it "accepts a 'size' argument" do + @object.alphanumeric(10).size.should == 10 + end + + it "uses the default size if 'nil' is given as size argument" do + @object.alphanumeric(nil).size.should == 16 + end + + it "raises an ArgumentError if the size is not numeric" do + -> { + @object.alphanumeric("10") + }.should raise_error(ArgumentError) + end + + it "does not coerce the size argument with #to_int" do + size = mock("size") + size.should_not_receive(:to_int) + -> { + @object.alphanumeric(size) + }.should raise_error(ArgumentError) + end + + ruby_version_is "3.3" do + it "accepts a 'chars' argument with the output alphabet" do + @object.alphanumeric(chars: ['a', 'b']).should =~ /\A[ab]+\z/ + end + + it "converts the elements of chars using #to_s" do + to_s = mock("to_s") + to_s.should_receive(:to_s).and_return("[mock to_s]") + # Using 1 value in chars results in an infinite loop + @object.alphanumeric(1, chars: [to_s, to_s]).should == "[mock to_s]" + end + end +end diff --git a/spec/ruby/library/rbconfig/rbconfig_spec.rb b/spec/ruby/library/rbconfig/rbconfig_spec.rb new file mode 100644 index 0000000000..b9a4588bf0 --- /dev/null +++ b/spec/ruby/library/rbconfig/rbconfig_spec.rb @@ -0,0 +1,163 @@ +require_relative '../../spec_helper' +require 'rbconfig' + +describe 'RbConfig::CONFIG' do + it 'values are all strings' do + RbConfig::CONFIG.each do |k, v| + k.should be_kind_of String + v.should be_kind_of String + end + end + + it 'has MAJOR, MINOR, TEENY, and PATCHLEVEL matching RUBY_VERSION and RUBY_PATCHLEVEL' do + major, minor, teeny = RUBY_VERSION.split('.') + RbConfig::CONFIG.values_at("MAJOR", "MINOR", "TEENY", "PATCHLEVEL").should == [ + major, minor, teeny, RUBY_PATCHLEVEL.to_s + ] + end + + # These directories have no meanings before the installation. + guard -> { RbConfig::TOPDIR } do + it "['rubylibdir'] returns the directory containing Ruby standard libraries" do + rubylibdir = RbConfig::CONFIG['rubylibdir'] + File.directory?(rubylibdir).should == true + File.should.exist?("#{rubylibdir}/fileutils.rb") + end + + it "['archdir'] returns the directory containing standard libraries C extensions" do + archdir = RbConfig::CONFIG['archdir'] + File.directory?(archdir).should == true + File.should.exist?("#{archdir}/etc.#{RbConfig::CONFIG['DLEXT']}") + end + + it "['sitelibdir'] is set and is part of $LOAD_PATH" do + sitelibdir = RbConfig::CONFIG['sitelibdir'] + sitelibdir.should be_kind_of String + $LOAD_PATH.map{|path| File.realpath(path) rescue path }.should.include? sitelibdir + end + end + + it "contains no frozen strings even with --enable-frozen-string-literal" do + ruby_exe(<<-RUBY, options: '--enable-frozen-string-literal').should == "Done\n" + require 'rbconfig' + RbConfig::CONFIG.each do |k, v| + if v.frozen? + puts "\#{k} Failure" + end + end + puts 'Done' + RUBY + end + + platform_is_not :windows do + it "['LIBRUBY'] is the same as LIBRUBY_SO if and only if ENABLE_SHARED" do + case RbConfig::CONFIG['ENABLE_SHARED'] + when 'yes' + RbConfig::CONFIG['LIBRUBY'].should == RbConfig::CONFIG['LIBRUBY_SO'] + when 'no' + RbConfig::CONFIG['LIBRUBY'].should_not == RbConfig::CONFIG['LIBRUBY_SO'] + end + end + end + + guard -> { RbConfig::TOPDIR } do + it "libdir/LIBRUBY_SO is the path to libruby and it exists if and only if ENABLE_SHARED" do + libdirname = RbConfig::CONFIG['LIBPATHENV'] == 'PATH' ? 'bindir' : + RbConfig::CONFIG['libdirname'] + libdir = RbConfig::CONFIG[libdirname] + libruby_so = "#{libdir}/#{RbConfig::CONFIG['LIBRUBY_SO']}" + case RbConfig::CONFIG['ENABLE_SHARED'] + when 'yes' + File.should.exist?(libruby_so) + when 'no' + File.should_not.exist?(libruby_so) + end + end + end + + platform_is :linux do + it "['AR'] exists and can be executed" do + ar = RbConfig::CONFIG.fetch('AR') + out = `#{ar} --version` + $?.should.success? + out.should_not be_empty + end + + it "['STRIP'] exists and can be executed" do + strip = RbConfig::CONFIG.fetch('STRIP') + copy = tmp("sh") + cp '/bin/sh', copy + begin + out = `#{strip} #{copy}` + $?.should.success? + ensure + rm_r copy + end + end + end + + guard -> { %w[aarch64 arm64].include? RbConfig::CONFIG['host_cpu'] } do + it "['host_cpu'] returns CPU architecture properly for AArch64" do + platform_is :darwin do + RbConfig::CONFIG['host_cpu'].should == 'arm64' + end + + platform_is_not :darwin do + RbConfig::CONFIG['host_cpu'].should == 'aarch64' + end + end + end + + guard -> { platform_is(:linux) || platform_is(:darwin) } do + it "['host_os'] returns a proper OS name or platform" do + platform_is :darwin do + RbConfig::CONFIG['host_os'].should.match?(/darwin/) + end + + platform_is :linux do + RbConfig::CONFIG['host_os'].should.match?(/linux/) + end + end + end +end + +describe "RbConfig::TOPDIR" do + it "either returns nil (if not installed) or the prefix" do + if RbConfig::TOPDIR + RbConfig::TOPDIR.should == RbConfig::CONFIG["prefix"] + else + RbConfig::TOPDIR.should == nil + end + end +end + +describe "RUBY_PLATFORM" do + it "RUBY_PLATFORM contains a proper CPU architecture" do + RUBY_PLATFORM.should.include? RbConfig::CONFIG['host_cpu'] + end + + guard -> { platform_is(:linux) || platform_is(:darwin) } do + it "RUBY_PLATFORM contains OS name" do + # don't use RbConfig::CONFIG['host_os'] as far as it could be slightly different, e.g. linux-gnu + platform_is(:linux) do + RUBY_PLATFORM.should.include? 'linux' + end + + platform_is(:darwin) do + RUBY_PLATFORM.should.include? 'darwin' + end + end + end +end + +describe "RUBY_DESCRIPTION" do + guard_not -> { RUBY_ENGINE == "ruby" && !RbConfig::TOPDIR } do + it "contains version" do + RUBY_DESCRIPTION.should.include? RUBY_VERSION + end + + it "contains RUBY_PLATFORM" do + RUBY_DESCRIPTION.should.include? RUBY_PLATFORM + end + end +end diff --git a/spec/ruby/library/rbconfig/sizeof/limits_spec.rb b/spec/ruby/library/rbconfig/sizeof/limits_spec.rb new file mode 100644 index 0000000000..776099da27 --- /dev/null +++ b/spec/ruby/library/rbconfig/sizeof/limits_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../../spec_helper' +require 'rbconfig/sizeof' + +describe "RbConfig::LIMITS" do + it "is a Hash" do + RbConfig::LIMITS.should be_kind_of(Hash) + end + + it "has string keys and numeric values" do + RbConfig::LIMITS.each do |key, value| + key.should be_kind_of String + value.should be_kind_of Numeric + end + end + + it "contains FIXNUM_MIN and FIXNUM_MAX" do + RbConfig::LIMITS["FIXNUM_MIN"].should < 0 + RbConfig::LIMITS["FIXNUM_MAX"].should > 0 + end + + it "contains CHAR_MIN and CHAR_MAX" do + RbConfig::LIMITS["CHAR_MIN"].should <= 0 + RbConfig::LIMITS["CHAR_MAX"].should > 0 + end + + it "contains SHRT_MIN and SHRT_MAX" do + RbConfig::LIMITS["SHRT_MIN"].should == -32768 + RbConfig::LIMITS["SHRT_MAX"].should == 32767 + end + + it "contains INT_MIN and INT_MAX" do + RbConfig::LIMITS["INT_MIN"].should < 0 + RbConfig::LIMITS["INT_MAX"].should > 0 + end + + it "contains LONG_MIN and LONG_MAX" do + RbConfig::LIMITS["LONG_MIN"].should < 0 + RbConfig::LIMITS["LONG_MAX"].should > 0 + end +end diff --git a/spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb b/spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb new file mode 100644 index 0000000000..f2582dc4fd --- /dev/null +++ b/spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' +require 'rbconfig/sizeof' + +describe "RbConfig::SIZEOF" do + it "is a Hash" do + RbConfig::SIZEOF.should be_kind_of(Hash) + end + + it "has string keys and integer values" do + RbConfig::SIZEOF.each do |key, value| + key.should be_kind_of String + value.should be_kind_of Integer + end + end + + it "contains the sizeof(void*)" do + (RbConfig::SIZEOF["void*"] * 8).should == PlatformGuard::POINTER_SIZE + end + + it "contains the sizeof(float) and sizeof(double)" do + RbConfig::SIZEOF["float"].should == 4 + RbConfig::SIZEOF["double"].should == 8 + end + + it "contains the size of short, int and long" do + RbConfig::SIZEOF["short"].should > 0 + RbConfig::SIZEOF["int"].should > 0 + RbConfig::SIZEOF["long"].should > 0 + end +end diff --git a/spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb b/spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb new file mode 100644 index 0000000000..521a750bf7 --- /dev/null +++ b/spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'rbconfig' + +describe "RbConfig::CONFIG['UNICODE_EMOJI_VERSION']" do + ruby_version_is ""..."3.4" do + it "is 15.0" do + RbConfig::CONFIG['UNICODE_EMOJI_VERSION'].should == "15.0" + end + end + + # Caution: ruby_version_is means is_or_later + ruby_version_is "4.0" do + it "is 17.0" do + RbConfig::CONFIG['UNICODE_EMOJI_VERSION'].should == "17.0" + end + end +end diff --git a/spec/ruby/library/rbconfig/unicode_version_spec.rb b/spec/ruby/library/rbconfig/unicode_version_spec.rb new file mode 100644 index 0000000000..5cdde74f79 --- /dev/null +++ b/spec/ruby/library/rbconfig/unicode_version_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'rbconfig' + +describe "RbConfig::CONFIG['UNICODE_VERSION']" do + ruby_version_is ""..."3.4" do + it "is 15.0.0" do + RbConfig::CONFIG['UNICODE_VERSION'].should == "15.0.0" + end + end + + # Caution: ruby_version_is means is_or_later + ruby_version_is "4.0" do + it "is 17.0.0" do + RbConfig::CONFIG['UNICODE_VERSION'].should == "17.0.0" + end + end +end diff --git a/spec/ruby/library/readline/basic_quote_characters_spec.rb b/spec/ruby/library/readline/basic_quote_characters_spec.rb new file mode 100644 index 0000000000..216899d875 --- /dev/null +++ b/spec/ruby/library/readline/basic_quote_characters_spec.rb @@ -0,0 +1,18 @@ +require_relative 'spec_helper' + +platform_is_not :darwin do + with_feature :readline do + describe "Readline.basic_quote_characters" do + it "returns not nil" do + Readline.basic_quote_characters.should_not be_nil + end + end + + describe "Readline.basic_quote_characters=" do + it "returns the passed string" do + Readline.basic_quote_characters = "test" + Readline.basic_quote_characters.should == "test" + end + end + end +end diff --git a/spec/ruby/library/readline/basic_word_break_characters_spec.rb b/spec/ruby/library/readline/basic_word_break_characters_spec.rb new file mode 100644 index 0000000000..daa0e1cb76 --- /dev/null +++ b/spec/ruby/library/readline/basic_word_break_characters_spec.rb @@ -0,0 +1,16 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.basic_word_break_characters" do + it "returns not nil" do + Readline.basic_word_break_characters.should_not be_nil + end + end + + describe "Readline.basic_word_break_characters=" do + it "returns the passed string" do + Readline.basic_word_break_characters = "test" + Readline.basic_word_break_characters.should == "test" + end + end +end diff --git a/spec/ruby/library/readline/completer_quote_characters_spec.rb b/spec/ruby/library/readline/completer_quote_characters_spec.rb new file mode 100644 index 0000000000..86c58f3cf6 --- /dev/null +++ b/spec/ruby/library/readline/completer_quote_characters_spec.rb @@ -0,0 +1,16 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.completer_quote_characters" do + it "returns nil" do + Readline.completer_quote_characters.should be_nil + end + end + + describe "Readline.completer_quote_characters=" do + it "returns the passed string" do + Readline.completer_quote_characters = "test" + Readline.completer_quote_characters.should == "test" + end + end +end diff --git a/spec/ruby/library/readline/completer_word_break_characters_spec.rb b/spec/ruby/library/readline/completer_word_break_characters_spec.rb new file mode 100644 index 0000000000..c72f1135c4 --- /dev/null +++ b/spec/ruby/library/readline/completer_word_break_characters_spec.rb @@ -0,0 +1,16 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.completer_word_break_characters" do + it "returns nil" do + Readline.completer_word_break_characters.should be_nil + end + end + + describe "Readline.completer_word_break_characters=" do + it "returns the passed string" do + Readline.completer_word_break_characters = "test" + Readline.completer_word_break_characters.should == "test" + end + end +end diff --git a/spec/ruby/library/readline/completion_append_character_spec.rb b/spec/ruby/library/readline/completion_append_character_spec.rb new file mode 100644 index 0000000000..615b523f4e --- /dev/null +++ b/spec/ruby/library/readline/completion_append_character_spec.rb @@ -0,0 +1,16 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.completion_append_character" do + it "returns not nil" do + Readline.completion_append_character.should_not be_nil + end + end + + describe "Readline.completion_append_character=" do + it "returns the first character of the passed string" do + Readline.completion_append_character = "test" + Readline.completion_append_character.should == "t" + end + end +end diff --git a/spec/ruby/library/readline/completion_case_fold_spec.rb b/spec/ruby/library/readline/completion_case_fold_spec.rb new file mode 100644 index 0000000000..966f5d6c79 --- /dev/null +++ b/spec/ruby/library/readline/completion_case_fold_spec.rb @@ -0,0 +1,18 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.completion_case_fold" do + it "returns nil" do + Readline.completion_case_fold.should be_nil + end + end + + describe "Readline.completion_case_fold=" do + it "returns the passed boolean" do + Readline.completion_case_fold = true + Readline.completion_case_fold.should == true + Readline.completion_case_fold = false + Readline.completion_case_fold.should == false + end + end +end diff --git a/spec/ruby/library/readline/completion_proc_spec.rb b/spec/ruby/library/readline/completion_proc_spec.rb new file mode 100644 index 0000000000..2d7a353ec5 --- /dev/null +++ b/spec/ruby/library/readline/completion_proc_spec.rb @@ -0,0 +1,22 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.completion_proc" do + it "returns nil" do + Readline.completion_proc.should be_nil + end + end + + describe "Readline.completion_proc=" do + it "returns the passed Proc" do + proc = Proc.new do |e| + end + Readline.completion_proc = proc + Readline.completion_proc.should == proc + end + + it "returns an ArgumentError if not given an Proc or #call" do + -> { Readline.completion_proc = "test" }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/readline/constants_spec.rb b/spec/ruby/library/readline/constants_spec.rb new file mode 100644 index 0000000000..8fee274866 --- /dev/null +++ b/spec/ruby/library/readline/constants_spec.rb @@ -0,0 +1,18 @@ +require_relative 'spec_helper' + +with_feature :readline do + # Note: additional specs for HISTORY are in 'history' subdir. + describe "Readline::HISTORY" do + it "is defined" do + Readline.const_defined?(:HISTORY).should == true + end + end + + describe "Readline::VERSION" do + it "is defined and is a non-empty String" do + Readline.const_defined?(:VERSION).should == true + Readline::VERSION.should be_kind_of(String) + Readline::VERSION.should_not be_empty + end + end +end diff --git a/spec/ruby/library/readline/emacs_editing_mode_spec.rb b/spec/ruby/library/readline/emacs_editing_mode_spec.rb new file mode 100644 index 0000000000..f7e8eda982 --- /dev/null +++ b/spec/ruby/library/readline/emacs_editing_mode_spec.rb @@ -0,0 +1,11 @@ +require_relative 'spec_helper' + +platform_is_not :darwin do + with_feature :readline do + describe "Readline.emacs_editing_mode" do + it "returns nil" do + Readline.emacs_editing_mode.should be_nil + end + end + end +end diff --git a/spec/ruby/library/readline/filename_quote_characters_spec.rb b/spec/ruby/library/readline/filename_quote_characters_spec.rb new file mode 100644 index 0000000000..de8ce700a8 --- /dev/null +++ b/spec/ruby/library/readline/filename_quote_characters_spec.rb @@ -0,0 +1,18 @@ +require_relative 'spec_helper' + +platform_is_not :darwin do + with_feature :readline do + describe "Readline.filename_quote_characters" do + it "returns nil" do + Readline.filename_quote_characters.should be_nil + end + end + + describe "Readline.filename_quote_characters=" do + it "returns the passed string" do + Readline.filename_quote_characters = "test" + Readline.filename_quote_characters.should == "test" + end + end + end +end diff --git a/spec/ruby/library/readline/history/append_spec.rb b/spec/ruby/library/readline/history/append_spec.rb new file mode 100644 index 0000000000..5383271374 --- /dev/null +++ b/spec/ruby/library/readline/history/append_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.<<" do + it "appends the given Object to the history" do + Readline::HISTORY << "1" + Readline::HISTORY.size.should == 1 + + Readline::HISTORY << "2" + Readline::HISTORY.size.should == 2 + + Readline::HISTORY.pop.should == "2" + Readline::HISTORY.pop.should == "1" + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock("Converted to String") + obj.should_receive(:to_str).and_return("converted") + + Readline::HISTORY << obj + Readline::HISTORY.pop.should == "converted" + end + + it "raises a TypeError when the passed Object can't be converted to a String" do + -> { Readline::HISTORY << mock("Object") }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/readline/history/delete_at_spec.rb b/spec/ruby/library/readline/history/delete_at_spec.rb new file mode 100644 index 0000000000..3bd577e75c --- /dev/null +++ b/spec/ruby/library/readline/history/delete_at_spec.rb @@ -0,0 +1,38 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.delete_at" do + it "deletes and returns the history entry at the specified index" do + Readline::HISTORY.push("1", "2", "3") + + Readline::HISTORY.delete_at(1).should == "2" + Readline::HISTORY.size.should == 2 + + Readline::HISTORY.delete_at(1).should == "3" + Readline::HISTORY.size.should == 1 + + Readline::HISTORY.delete_at(0).should == "1" + Readline::HISTORY.size.should == 0 + + + Readline::HISTORY.push("1", "2", "3", "4") + + Readline::HISTORY.delete_at(-2).should == "3" + Readline::HISTORY.size.should == 3 + + Readline::HISTORY.delete_at(-2).should == "2" + Readline::HISTORY.size.should == 2 + + Readline::HISTORY.delete_at(0).should == "1" + Readline::HISTORY.size.should == 1 + + Readline::HISTORY.delete_at(0).should == "4" + Readline::HISTORY.size.should == 0 + end + + it "raises an IndexError when the given index is greater than the history size" do + -> { Readline::HISTORY.delete_at(10) }.should raise_error(IndexError) + -> { Readline::HISTORY.delete_at(-10) }.should raise_error(IndexError) + end + end +end diff --git a/spec/ruby/library/readline/history/each_spec.rb b/spec/ruby/library/readline/history/each_spec.rb new file mode 100644 index 0000000000..aa48dd46df --- /dev/null +++ b/spec/ruby/library/readline/history/each_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.each" do + before :each do + Readline::HISTORY.push("1", "2", "3") + end + + after :each do + Readline::HISTORY.pop + Readline::HISTORY.pop + Readline::HISTORY.pop + end + + it "yields each item in the history" do + result = [] + Readline::HISTORY.each do |x| + result << x + end + result.should == ["1", "2", "3"] + end + end +end diff --git a/spec/ruby/library/readline/history/element_reference_spec.rb b/spec/ruby/library/readline/history/element_reference_spec.rb new file mode 100644 index 0000000000..0a74f3d62d --- /dev/null +++ b/spec/ruby/library/readline/history/element_reference_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.[]" do + before :each do + Readline::HISTORY.push("1", "2", "3") + end + + after :each do + Readline::HISTORY.pop + Readline::HISTORY.pop + Readline::HISTORY.pop + end + + it "returns the history item at the passed index" do + Readline::HISTORY[0].should == "1" + Readline::HISTORY[1].should == "2" + Readline::HISTORY[2].should == "3" + + Readline::HISTORY[-1].should == "3" + Readline::HISTORY[-2].should == "2" + Readline::HISTORY[-3].should == "1" + end + + it "raises an IndexError when there is no item at the passed index" do + -> { Readline::HISTORY[-10] }.should raise_error(IndexError) + -> { Readline::HISTORY[-9] }.should raise_error(IndexError) + -> { Readline::HISTORY[-8] }.should raise_error(IndexError) + + -> { Readline::HISTORY[8] }.should raise_error(IndexError) + -> { Readline::HISTORY[9] }.should raise_error(IndexError) + -> { Readline::HISTORY[10] }.should raise_error(IndexError) + end + end +end diff --git a/spec/ruby/library/readline/history/element_set_spec.rb b/spec/ruby/library/readline/history/element_set_spec.rb new file mode 100644 index 0000000000..776adaacd1 --- /dev/null +++ b/spec/ruby/library/readline/history/element_set_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.[]=" do + before :each do + Readline::HISTORY.push("1", "2", "3") + end + + after :each do + Readline::HISTORY.pop + Readline::HISTORY.pop + Readline::HISTORY.pop + end + + it "returns the new value for the passed index" do + (Readline::HISTORY[1] = "second test").should == "second test" + end + + it "raises an IndexError when there is no item at the passed positive index" do + -> { Readline::HISTORY[10] = "test" }.should raise_error(IndexError) + end + + it "sets the item at the given index" do + Readline::HISTORY[0] = "test" + Readline::HISTORY[0].should == "test" + + Readline::HISTORY[1] = "second test" + Readline::HISTORY[1].should == "second test" + end + + it "raises an IndexError when there is no item at the passed negative index" do + -> { Readline::HISTORY[10] = "test" }.should raise_error(IndexError) + end + end +end diff --git a/spec/ruby/library/readline/history/empty_spec.rb b/spec/ruby/library/readline/history/empty_spec.rb new file mode 100644 index 0000000000..31d01d9601 --- /dev/null +++ b/spec/ruby/library/readline/history/empty_spec.rb @@ -0,0 +1,13 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.empty?" do + it "returns true when the history is empty" do + Readline::HISTORY.should be_empty + Readline::HISTORY.push("test") + Readline::HISTORY.should_not be_empty + Readline::HISTORY.pop + Readline::HISTORY.should be_empty + end + end +end diff --git a/spec/ruby/library/readline/history/history_spec.rb b/spec/ruby/library/readline/history/history_spec.rb new file mode 100644 index 0000000000..927dd52ebf --- /dev/null +++ b/spec/ruby/library/readline/history/history_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY" do + it "is extended with the Enumerable module" do + Readline::HISTORY.should be_kind_of(Enumerable) + end + end +end diff --git a/spec/ruby/library/readline/history/length_spec.rb b/spec/ruby/library/readline/history/length_spec.rb new file mode 100644 index 0000000000..9427d10a00 --- /dev/null +++ b/spec/ruby/library/readline/history/length_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :readline do + require_relative 'shared/size' + + describe "Readline::HISTORY.length" do + it_behaves_like :readline_history_size, :length + end +end diff --git a/spec/ruby/library/readline/history/pop_spec.rb b/spec/ruby/library/readline/history/pop_spec.rb new file mode 100644 index 0000000000..156a8a06f8 --- /dev/null +++ b/spec/ruby/library/readline/history/pop_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.pop" do + it "returns nil when the history is empty" do + Readline::HISTORY.pop.should be_nil + end + + it "returns and removes the last item from the history" do + Readline::HISTORY.push("1", "2", "3") + Readline::HISTORY.size.should == 3 + + Readline::HISTORY.pop.should == "3" + Readline::HISTORY.size.should == 2 + + Readline::HISTORY.pop.should == "2" + Readline::HISTORY.size.should == 1 + + Readline::HISTORY.pop.should == "1" + Readline::HISTORY.size.should == 0 + end + end +end diff --git a/spec/ruby/library/readline/history/push_spec.rb b/spec/ruby/library/readline/history/push_spec.rb new file mode 100644 index 0000000000..53505ccba6 --- /dev/null +++ b/spec/ruby/library/readline/history/push_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.push" do + it "pushes all passed Objects into the history" do + Readline::HISTORY.push("1", "2", "3") + Readline::HISTORY.size.should == 3 + + Readline::HISTORY.pop.should == "3" + Readline::HISTORY.pop.should == "2" + Readline::HISTORY.pop.should == "1" + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock("Converted to String") + obj.should_receive(:to_str).and_return("converted") + + Readline::HISTORY.push(obj) + Readline::HISTORY.pop.should == "converted" + end + + it "raises a TypeError when the passed Object can't be converted to a String" do + -> { Readline::HISTORY.push(mock("Object")) }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/readline/history/shared/size.rb b/spec/ruby/library/readline/history/shared/size.rb new file mode 100644 index 0000000000..1d6df86f78 --- /dev/null +++ b/spec/ruby/library/readline/history/shared/size.rb @@ -0,0 +1,14 @@ +describe :readline_history_size, shared: true do + it "returns the size of the history" do + Readline::HISTORY.send(@method).should == 0 + Readline::HISTORY.push("1", "2", "") + Readline::HISTORY.send(@method).should == 3 + + Readline::HISTORY.pop + Readline::HISTORY.send(@method).should == 2 + + Readline::HISTORY.pop + Readline::HISTORY.pop + Readline::HISTORY.send(@method).should == 0 + end +end diff --git a/spec/ruby/library/readline/history/shift_spec.rb b/spec/ruby/library/readline/history/shift_spec.rb new file mode 100644 index 0000000000..9aad7d5399 --- /dev/null +++ b/spec/ruby/library/readline/history/shift_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.shift" do + it "returns nil when the history is empty" do + Readline::HISTORY.shift.should be_nil + end + + it "returns and removes the first item from the history" do + Readline::HISTORY.push("1", "2", "3") + Readline::HISTORY.size.should == 3 + + Readline::HISTORY.shift.should == "1" + Readline::HISTORY.size.should == 2 + + Readline::HISTORY.shift.should == "2" + Readline::HISTORY.size.should == 1 + + Readline::HISTORY.shift.should == "3" + Readline::HISTORY.size.should == 0 + end + end +end diff --git a/spec/ruby/library/readline/history/size_spec.rb b/spec/ruby/library/readline/history/size_spec.rb new file mode 100644 index 0000000000..c55253ccea --- /dev/null +++ b/spec/ruby/library/readline/history/size_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :readline do + require_relative 'shared/size' + + describe "Readline::HISTORY.size" do + it_behaves_like :readline_history_size, :size + end +end diff --git a/spec/ruby/library/readline/history/to_s_spec.rb b/spec/ruby/library/readline/history/to_s_spec.rb new file mode 100644 index 0000000000..ee338f2ab4 --- /dev/null +++ b/spec/ruby/library/readline/history/to_s_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :readline do + describe "Readline::HISTORY.to_s" do + it "returns 'HISTORY'" do + Readline::HISTORY.to_s.should == "HISTORY" + end + end +end diff --git a/spec/ruby/library/readline/readline_spec.rb b/spec/ruby/library/readline/readline_spec.rb new file mode 100644 index 0000000000..6e349ad543 --- /dev/null +++ b/spec/ruby/library/readline/readline_spec.rb @@ -0,0 +1,26 @@ +require_relative 'spec_helper' + +with_feature :readline do + describe "Readline.readline" do + before :each do + @file = tmp('readline') + @out = tmp('out.txt') + touch(@file) { |f| + f.puts "test" + } + @options = { options: "-rreadline", args: [@out, "< #{@file}"] } + end + + after :each do + rm_r @file, @out + end + + # Somehow those specs block on Windows + platform_is_not :windows do + it "returns the input string" do + ruby_exe('File.write ARGV[0], Readline.readline', @options) + File.read(@out).should == "test" + end + end + end +end diff --git a/spec/ruby/library/readline/spec_helper.rb b/spec/ruby/library/readline/spec_helper.rb new file mode 100644 index 0000000000..32d820f7ac --- /dev/null +++ b/spec/ruby/library/readline/spec_helper.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' + +begin + require 'readline' +rescue LoadError +else + # rb-readline and reline behave quite differently + unless defined?(RbReadline) or defined?(Reline) + MSpec.enable_feature :readline + end +end diff --git a/spec/ruby/library/readline/vi_editing_mode_spec.rb b/spec/ruby/library/readline/vi_editing_mode_spec.rb new file mode 100644 index 0000000000..6622962ceb --- /dev/null +++ b/spec/ruby/library/readline/vi_editing_mode_spec.rb @@ -0,0 +1,11 @@ +require_relative 'spec_helper' + +platform_is_not :darwin do + with_feature :readline do + describe "Readline.vi_editing_mode" do + it "returns nil" do + Readline.vi_editing_mode.should be_nil + end + end + end +end diff --git a/spec/ruby/library/resolv/fixtures/hosts b/spec/ruby/library/resolv/fixtures/hosts new file mode 100644 index 0000000000..a50f3d6a69 --- /dev/null +++ b/spec/ruby/library/resolv/fixtures/hosts @@ -0,0 +1 @@ +127.0.0.1 localhost localhost4 diff --git a/spec/ruby/library/resolv/get_address_spec.rb b/spec/ruby/library/resolv/get_address_spec.rb new file mode 100644 index 0000000000..ecc2cdf7de --- /dev/null +++ b/spec/ruby/library/resolv/get_address_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'resolv' + +describe "Resolv#getaddress" do + it "resolves localhost" do + hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts")) + res = Resolv.new([hosts]) + + res.getaddress("localhost").should == "127.0.0.1" + res.getaddress("localhost4").should == "127.0.0.1" + end + + it "raises ResolvError if the name can not be looked up" do + res = Resolv.new([]) + -> { + res.getaddress("should.raise.error.") + }.should raise_error(Resolv::ResolvError) + end +end diff --git a/spec/ruby/library/resolv/get_addresses_spec.rb b/spec/ruby/library/resolv/get_addresses_spec.rb new file mode 100644 index 0000000000..b84f29b7da --- /dev/null +++ b/spec/ruby/library/resolv/get_addresses_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'resolv' + +describe "Resolv#getaddresses" do + it "resolves localhost" do + hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts")) + res = Resolv.new([hosts]) + + res.getaddresses("localhost").should == ["127.0.0.1"] + res.getaddresses("localhost4").should == ["127.0.0.1"] + end +end diff --git a/spec/ruby/library/resolv/get_name_spec.rb b/spec/ruby/library/resolv/get_name_spec.rb new file mode 100644 index 0000000000..3ef97a2cea --- /dev/null +++ b/spec/ruby/library/resolv/get_name_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require 'resolv' + +describe "Resolv#getname" do + it "resolves 127.0.0.1" do + hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts")) + res = Resolv.new([hosts]) + + res.getname("127.0.0.1").should == "localhost" + end + + it "raises ResolvError when there is no result" do + res = Resolv.new([]) + -> { + res.getname("should.raise.error") + }.should raise_error(Resolv::ResolvError) + end +end diff --git a/spec/ruby/library/resolv/get_names_spec.rb b/spec/ruby/library/resolv/get_names_spec.rb new file mode 100644 index 0000000000..c405360615 --- /dev/null +++ b/spec/ruby/library/resolv/get_names_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require 'resolv' + +describe "Resolv#getnames" do + it "resolves 127.0.0.1" do + hosts = Resolv::Hosts.new(fixture(__FILE__ , "hosts")) + res = Resolv.new([hosts]) + + names = res.getnames("127.0.0.1").should == ["localhost", "localhost4"] + end +end diff --git a/spec/ruby/library/ripper/lex_spec.rb b/spec/ruby/library/ripper/lex_spec.rb new file mode 100644 index 0000000000..97cfb06904 --- /dev/null +++ b/spec/ruby/library/ripper/lex_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'ripper' + +describe "Ripper.lex" do + it "lexes a simple method declaration" do + expected = [ + [[1, 0], :on_kw, "def", 'FNAME'], + [[1, 3], :on_sp, " ", 'FNAME'], + [[1, 4], :on_ident, "m", 'ENDFN'], + [[1, 5], :on_lparen, "(", 'BEG|LABEL'], + [[1, 6], :on_ident, "a", 'ARG'], + [[1, 7], :on_rparen, ")", 'ENDFN'], + [[1, 8], :on_sp, " ", 'BEG'], + [[1, 9], :on_kw, "nil", 'END'], + [[1, 12], :on_sp, " ", 'END'], + [[1, 13], :on_kw, "end", 'END'] + ] + lexed = Ripper.lex("def m(a) nil end") + lexed.map { |e| + e[0...-1] + [e[-1].to_s.split('|').map { |s| s.sub(/^EXPR_/, '') }.join('|')] + }.should == expected + end +end diff --git a/spec/ruby/library/ripper/sexp_spec.rb b/spec/ruby/library/ripper/sexp_spec.rb new file mode 100644 index 0000000000..6c69624c65 --- /dev/null +++ b/spec/ruby/library/ripper/sexp_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'ripper' + +describe "Ripper.sexp" do + it "returns an s-expression for a method declaration" do + expected = [:program, + [[:def, + [:@ident, "hello", [1, 4]], + [:params, nil, nil, nil, nil, nil, nil, nil], + [:bodystmt, [[:@int, "42", [1, 11]]], nil, nil, nil]]]] + Ripper.sexp("def hello; 42; end").should == expected + end +end diff --git a/spec/ruby/library/rubygems/gem/bin_path_spec.rb b/spec/ruby/library/rubygems/gem/bin_path_spec.rb new file mode 100644 index 0000000000..0b8c4db08b --- /dev/null +++ b/spec/ruby/library/rubygems/gem/bin_path_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../../spec_helper' +require 'rubygems' + +describe "Gem.bin_path" do + before :each do + @bundle_gemfile = ENV['BUNDLE_GEMFILE'] + ENV['BUNDLE_GEMFILE'] = tmp("no-gemfile") + end + + after :each do + ENV['BUNDLE_GEMFILE'] = @bundle_gemfile + end + + platform_is_not :windows do + it "finds executables of default gems, which are the only files shipped for default gems" do + # For instance, Gem.bin_path("bundler", "bundle") is used by rails new + + if Gem.respond_to? :default_specifications_dir + default_specifications_dir = Gem.default_specifications_dir + else + default_specifications_dir = Gem::Specification.default_specifications_dir + end + + skip "Could not find the default gemspecs" unless Dir.exist?(default_specifications_dir) + skip "default_specifications_dir mismatch with GEM_HOME" if ENV["GEM_HOME"] && !default_specifications_dir.start_with?(ENV['GEM_HOME']) + + Gem::Specification.each_spec([default_specifications_dir]) do |spec| + spec.executables.each do |exe| + path = Gem.bin_path(spec.name, exe) + File.should.exist?(path) + end + end + end + end +end diff --git a/spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb b/spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb new file mode 100644 index 0000000000..9b37eaa43c --- /dev/null +++ b/spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'rubygems' + +describe "Gem.load_path_insert_index" do + guard -> { RbConfig::TOPDIR } do + it "is set for an installed Ruby" do + Gem.load_path_insert_index.should be_kind_of Integer + end + end +end diff --git a/spec/ruby/library/securerandom/base64_spec.rb b/spec/ruby/library/securerandom/base64_spec.rb new file mode 100644 index 0000000000..34cd419ce2 --- /dev/null +++ b/spec/ruby/library/securerandom/base64_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' + +require 'securerandom' + +describe "SecureRandom.base64" do + it "generates a random base64 string out of specified number of random bytes" do + (16..128).each do |idx| + base64 = SecureRandom.base64(idx) + base64.should be_kind_of(String) + base64.length.should < 2 * idx + base64.should =~ /^[A-Za-z0-9\+\/]+={0,2}$/ + end + + base64 = SecureRandom.base64(16.5) + base64.should be_kind_of(String) + base64.length.should < 2 * 16 + end + + it "returns an empty string when argument is 0" do + SecureRandom.base64(0).should == "" + end + + it "generates different base64 strings with subsequent invocations" do + # quick and dirty check, but good enough + values = [] + 256.times do + base64 = SecureRandom.base64 + # make sure the random values are not repeating + values.include?(base64).should == false + values << base64 + end + end + + it "generates a random base64 string out of 32 random bytes" do + SecureRandom.base64.should be_kind_of(String) + SecureRandom.base64.length.should < 32 * 2 + end + + it "treats nil argument as default one and generates a random base64 string" do + SecureRandom.base64(nil).should be_kind_of(String) + SecureRandom.base64(nil).length.should < 32 * 2 + end + + it "raises ArgumentError on negative arguments" do + -> { + SecureRandom.base64(-1) + }.should raise_error(ArgumentError) + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(5) + SecureRandom.base64(obj).size.should < 10 + end +end diff --git a/spec/ruby/library/securerandom/bytes_spec.rb b/spec/ruby/library/securerandom/bytes_spec.rb new file mode 100644 index 0000000000..a1ab836d16 --- /dev/null +++ b/spec/ruby/library/securerandom/bytes_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative '../../core/random/shared/bytes' + +require 'securerandom' + +describe "SecureRandom.bytes" do + it_behaves_like :random_bytes, :bytes, SecureRandom +end diff --git a/spec/ruby/library/securerandom/hex_spec.rb b/spec/ruby/library/securerandom/hex_spec.rb new file mode 100644 index 0000000000..bdb920b217 --- /dev/null +++ b/spec/ruby/library/securerandom/hex_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' + +require 'securerandom' + +describe "SecureRandom.hex" do + it "generates a random hex string of length twice the specified argument" do + (1..64).each do |idx| + hex = SecureRandom.hex(idx) + hex.should be_kind_of(String) + hex.length.should == 2 * idx + end + + base64 = SecureRandom.hex(5.5) + base64.should be_kind_of(String) + base64.length.should eql(10) + end + + it "returns an empty string when argument is 0" do + SecureRandom.hex(0).should == "" + end + + it "generates different hex strings with subsequent invocations" do + # quick and dirty check, but good enough + values = [] + 256.times do + hex = SecureRandom.hex + # make sure the random values are not repeating + values.include?(hex).should == false + values << hex + end + end + + it "generates a random hex string of length 32 if no argument is provided" do + SecureRandom.hex.should be_kind_of(String) + SecureRandom.hex.length.should == 32 + end + + it "treats nil argument as default one and generates a random hex string of length 32" do + SecureRandom.hex(nil).should be_kind_of(String) + SecureRandom.hex(nil).length.should == 32 + end + + it "raises ArgumentError on negative arguments" do + -> { + SecureRandom.hex(-1) + }.should raise_error(ArgumentError) + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(5) + SecureRandom.hex(obj).size.should eql(10) + end +end diff --git a/spec/ruby/library/securerandom/random_bytes_spec.rb b/spec/ruby/library/securerandom/random_bytes_spec.rb new file mode 100644 index 0000000000..ed3a02255c --- /dev/null +++ b/spec/ruby/library/securerandom/random_bytes_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require_relative '../../core/random/shared/bytes' + +require 'securerandom' + +describe "SecureRandom.random_bytes" do + it_behaves_like :random_bytes, :random_bytes, SecureRandom + + it "generates a random binary string of length 16 if no argument is provided" do + bytes = SecureRandom.random_bytes + bytes.should be_kind_of(String) + bytes.length.should == 16 + end + + it "generates a random binary string of length 16 if argument is nil" do + bytes = SecureRandom.random_bytes(nil) + bytes.should be_kind_of(String) + bytes.length.should == 16 + end + + it "generates a random binary string of specified length" do + (1..64).each do |idx| + bytes = SecureRandom.random_bytes(idx) + bytes.should be_kind_of(String) + bytes.length.should == idx + end + + SecureRandom.random_bytes(2.2).length.should eql(2) + end + + it "generates different binary strings with subsequent invocations" do + # quick and dirty check, but good enough + values = [] + 256.times do + val = SecureRandom.random_bytes + # make sure the random bytes are not repeating + values.include?(val).should == false + values << val + end + end + + it "raises ArgumentError on negative arguments" do + -> { + SecureRandom.random_bytes(-1) + }.should raise_error(ArgumentError) + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(5) + SecureRandom.random_bytes(obj).size.should eql(5) + end +end diff --git a/spec/ruby/library/securerandom/random_number_spec.rb b/spec/ruby/library/securerandom/random_number_spec.rb new file mode 100644 index 0000000000..bb25bc496e --- /dev/null +++ b/spec/ruby/library/securerandom/random_number_spec.rb @@ -0,0 +1,97 @@ +require_relative '../../spec_helper' +require_relative '../../core/random/shared/rand' + +require 'securerandom' + +describe "SecureRandom.random_number" do + it_behaves_like :random_number, :rand, SecureRandom + it_behaves_like :random_number, :random_number, SecureRandom + + it "generates a random positive number smaller then the positive integer argument" do + (1..64).each do |idx| + num = SecureRandom.random_number(idx) + num.should be_kind_of(Integer) + 0.should <= num + num.should < idx + end + end + + it "generates a random (potentially bignum) integer value for bignum argument" do + max = 12345678901234567890 + 11.times do + num = SecureRandom.random_number max + num.should be_kind_of(Integer) + 0.should <= num + num.should < max + end + end + + it "generates a random float number between 0.0 and 1.0 if no argument provided" do + 64.times do + num = SecureRandom.random_number + num.should be_kind_of(Float) + 0.0.should <= num + num.should < 1.0 + end + end + + it "generates a random value in given (integer) range limits" do + 64.times do + num = SecureRandom.random_number 11...13 + num.should be_kind_of(Integer) + 11.should <= num + num.should < 13 + end + end + + it "generates a random value in given big (integer) range limits" do + lower = 12345678901234567890 + upper = 12345678901234567890 + 5 + 32.times do + num = SecureRandom.random_number lower..upper + num.should be_kind_of(Integer) + lower.should <= num + num.should <= upper + end + end + + it "generates a random value in given (float) range limits" do + 64.times do + num = SecureRandom.random_number 0.6..0.9 + num.should be_kind_of(Float) + 0.6.should <= num + num.should <= 0.9 + end + end + + it "generates a random float number between 0.0 and 1.0 if argument is negative" do + num = SecureRandom.random_number(-10) + num.should be_kind_of(Float) + 0.0.should <= num + num.should < 1.0 + end + + it "generates a random float number between 0.0 and 1.0 if argument is negative float" do + num = SecureRandom.random_number(-11.1) + num.should be_kind_of(Float) + 0.0.should <= num + num.should < 1.0 + end + + it "generates different float numbers with subsequent invocations" do + # quick and dirty check, but good enough + values = [] + 256.times do + val = SecureRandom.random_number + # make sure the random values are not repeating + values.should_not include(val) + values << val + end + end + + it "raises ArgumentError if the argument is non-numeric" do + -> { + SecureRandom.random_number(Object.new) + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/shellwords/shellwords_spec.rb b/spec/ruby/library/shellwords/shellwords_spec.rb new file mode 100644 index 0000000000..fe86b6faab --- /dev/null +++ b/spec/ruby/library/shellwords/shellwords_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require 'shellwords' + +describe "Shellwords#shellwords" do + it "honors quoted strings" do + Shellwords.shellwords('a "b b" a').should == ['a', 'b b', 'a'] + end + + it "honors escaped double quotes" do + Shellwords.shellwords('a "\"b\" c" d').should == ['a', '"b" c', 'd'] + end + + it "honors escaped single quotes" do + Shellwords.shellwords("a \"'b' c\" d").should == ['a', "'b' c", 'd'] + end + + it "honors escaped spaces" do + Shellwords.shellwords('a b\ c d').should == ['a', 'b c', 'd'] + end + + it "raises ArgumentError when double quoted strings are misquoted" do + -> { Shellwords.shellwords('a "b c d e') }.should raise_error(ArgumentError) + end + + it "raises ArgumentError when single quoted strings are misquoted" do + -> { Shellwords.shellwords("a 'b c d e") }.should raise_error(ArgumentError) + end + + # https://bugs.ruby-lang.org/issues/10055 + it "matches POSIX sh behavior for backslashes within double quoted strings" do + Shellwords.shellsplit('printf "%s\n"').should == ['printf', '%s\n'] + end +end diff --git a/spec/ruby/library/singleton/allocate_spec.rb b/spec/ruby/library/singleton/allocate_spec.rb new file mode 100644 index 0000000000..6a1512d53b --- /dev/null +++ b/spec/ruby/library/singleton/allocate_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Singleton.allocate" do + it "is a private method" do + -> { SingletonSpecs::MyClass.allocate }.should raise_error(NoMethodError) + end +end diff --git a/spec/ruby/library/singleton/clone_spec.rb b/spec/ruby/library/singleton/clone_spec.rb new file mode 100644 index 0000000000..3635bcd594 --- /dev/null +++ b/spec/ruby/library/singleton/clone_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Singleton#clone" do + it "is prevented" do + -> { SingletonSpecs::MyClass.instance.clone }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/singleton/dump_spec.rb b/spec/ruby/library/singleton/dump_spec.rb new file mode 100644 index 0000000000..333e3bc4b0 --- /dev/null +++ b/spec/ruby/library/singleton/dump_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Singleton#_dump" do + + it "returns an empty string" do + SingletonSpecs::MyClass.instance.send(:_dump).should == "" + end + + it "returns an empty string from a singleton subclass" do + SingletonSpecs::MyClassChild.instance.send(:_dump).should == "" + end + +end diff --git a/spec/ruby/library/singleton/dup_spec.rb b/spec/ruby/library/singleton/dup_spec.rb new file mode 100644 index 0000000000..13d5a213e9 --- /dev/null +++ b/spec/ruby/library/singleton/dup_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Singleton#dup" do + it "is prevented" do + -> { SingletonSpecs::MyClass.instance.dup }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/singleton/fixtures/classes.rb b/spec/ruby/library/singleton/fixtures/classes.rb new file mode 100644 index 0000000000..c718ebaec8 --- /dev/null +++ b/spec/ruby/library/singleton/fixtures/classes.rb @@ -0,0 +1,18 @@ +require 'singleton' + +module SingletonSpecs + class MyClass + attr_accessor :data + include Singleton + end + + class NewSpec + include Singleton + end + + class MyClassChild < MyClass + end + + class NotInstantiated < MyClass + end +end diff --git a/spec/ruby/library/singleton/instance_spec.rb b/spec/ruby/library/singleton/instance_spec.rb new file mode 100644 index 0000000000..1679728d4c --- /dev/null +++ b/spec/ruby/library/singleton/instance_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Singleton.instance" do + it "returns an instance of the singleton class" do + SingletonSpecs::MyClass.instance.should be_kind_of(SingletonSpecs::MyClass) + end + + it "returns the same instance for multiple calls to instance" do + SingletonSpecs::MyClass.instance.should equal(SingletonSpecs::MyClass.instance) + end + + it "returns an instance of the singleton's subclasses" do + SingletonSpecs::MyClassChild.instance.should be_kind_of(SingletonSpecs::MyClassChild) + end + + it "returns the same instance for multiple class to instance on subclasses" do + SingletonSpecs::MyClassChild.instance.should equal(SingletonSpecs::MyClassChild.instance) + end + + it "returns an instance of the singleton's clone" do + klone = SingletonSpecs::MyClassChild.clone + klone.instance.should be_kind_of(klone) + end + + it "returns the same instance for multiple class to instance on clones" do + klone = SingletonSpecs::MyClassChild.clone + klone.instance.should equal(klone.instance) + end +end diff --git a/spec/ruby/library/singleton/load_spec.rb b/spec/ruby/library/singleton/load_spec.rb new file mode 100644 index 0000000000..4c753f9e7a --- /dev/null +++ b/spec/ruby/library/singleton/load_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +# TODO: change to a.should be_equal(b) +# TODO: write spec for cloning classes and calling private methods +# TODO: write spec for private_methods not showing up via extended +describe "Singleton._load" do + it "returns the singleton instance for anything passed in" do + klass = SingletonSpecs::MyClass + klass._load("").should equal(klass.instance) + klass._load("42").should equal(klass.instance) + klass._load(42).should equal(klass.instance) + end + + it "returns the singleton instance for anything passed in to subclass" do + subklass = SingletonSpecs::MyClassChild + subklass._load("").should equal(subklass.instance) + subklass._load("42").should equal(subklass.instance) + subklass._load(42).should equal(subklass.instance) + end +end diff --git a/spec/ruby/library/singleton/new_spec.rb b/spec/ruby/library/singleton/new_spec.rb new file mode 100644 index 0000000000..2f45db819c --- /dev/null +++ b/spec/ruby/library/singleton/new_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Singleton.new" do + it "is a private method" do + -> { SingletonSpecs::NewSpec.new }.should raise_error(NoMethodError) + end +end diff --git a/spec/ruby/library/socket/addrinfo/afamily_spec.rb b/spec/ruby/library/socket/addrinfo/afamily_spec.rb new file mode 100644 index 0000000000..5d075be057 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/afamily_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +describe "Addrinfo#afamily" do + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns Socket::AF_INET" do + @addrinfo.afamily.should == Socket::AF_INET + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns Socket::AF_INET6" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::AF_UNIX" do + @addrinfo.afamily.should == Socket::AF_UNIX + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/bind_spec.rb b/spec/ruby/library/socket/addrinfo/bind_spec.rb new file mode 100644 index 0000000000..6f78890a4d --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/bind_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Addrinfo#bind" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 0) + end + + after :each do + @socket.close unless @socket.closed? + end + + it "returns a bound socket when no block is given" do + @socket = @addrinfo.bind + @socket.should be_kind_of(Socket) + @socket.closed?.should be_false + end + + it "yields the socket if a block is given" do + @addrinfo.bind do |sock| + @socket = sock + sock.should be_kind_of(Socket) + end + @socket.closed?.should be_true + end + +end diff --git a/spec/ruby/library/socket/addrinfo/canonname_spec.rb b/spec/ruby/library/socket/addrinfo/canonname_spec.rb new file mode 100644 index 0000000000..a1cc8b3980 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/canonname_spec.rb @@ -0,0 +1,27 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Addrinfo#canonname" do + + before :each do + @addrinfos = Addrinfo.getaddrinfo("localhost", 80, :INET, :STREAM, nil, Socket::AI_CANONNAME) + end + + it "returns the canonical name for a host" do + canonname = @addrinfos.map { |a| a.canonname }.find { |name| name and name.include?("localhost") } + if canonname + canonname.should include("localhost") + else + canonname.should == nil + end + end + + describe 'when the canonical name is not available' do + it 'returns nil' do + addr = Addrinfo.new(Socket.sockaddr_in(0, '127.0.0.1')) + + addr.canonname.should be_nil + end + end + +end diff --git a/spec/ruby/library/socket/addrinfo/connect_from_spec.rb b/spec/ruby/library/socket/addrinfo/connect_from_spec.rb new file mode 100644 index 0000000000..55fce2e159 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/connect_from_spec.rb @@ -0,0 +1,75 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#connect_from' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + @addr = Addrinfo.tcp(ip_address, @port) + end + + after do + @socket.close if @socket + @server.close + end + + describe 'using separate arguments' do + it 'returns a Socket when no block is given' do + @socket = @addr.connect_from(ip_address, 0) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_from(ip_address, 0) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_from(ip_address, 0, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the socket to the local address' do + @socket = @addr.connect_from(ip_address, 0) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + + describe 'using an Addrinfo as the 1st argument' do + before do + @from_addr = Addrinfo.tcp(ip_address, 0) + end + + it 'returns a Socket when no block is given' do + @socket = @addr.connect_from(@from_addr) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_from(@from_addr) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_from(@from_addr, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the socket to the local address' do + @socket = @addr.connect_from(@from_addr) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/connect_spec.rb b/spec/ruby/library/socket/addrinfo/connect_spec.rb new file mode 100644 index 0000000000..1c2dc609ca --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/connect_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#connect' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + end + + after do + @socket.close if @socket + @server.close + end + + it 'returns a Socket when no block is given' do + addr = Addrinfo.tcp(ip_address, @port) + @socket = addr.connect + @socket.should be_an_instance_of(Socket) + end + + it 'yields a Socket when a block is given' do + addr = Addrinfo.tcp(ip_address, @port) + addr.connect do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'accepts a Hash of options' do + addr = Addrinfo.tcp(ip_address, @port) + @socket = addr.connect(timeout: 2) + @socket.should be_an_instance_of(Socket) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/connect_to_spec.rb b/spec/ruby/library/socket/addrinfo/connect_to_spec.rb new file mode 100644 index 0000000000..69666da19b --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/connect_to_spec.rb @@ -0,0 +1,75 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#connect_to' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + @addr = Addrinfo.tcp(ip_address, 0) + end + + after do + @socket.close if @socket + @server.close + end + + describe 'using separate arguments' do + it 'returns a Socket when no block is given' do + @socket = @addr.connect_to(ip_address, @port) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_to(ip_address, @port) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_to(ip_address, @port, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the Addrinfo to the local address' do + @socket = @addr.connect_to(ip_address, @port) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + + describe 'using an Addrinfo as the 1st argument' do + before do + @to_addr = Addrinfo.tcp(ip_address, @port) + end + + it 'returns a Socket when no block is given' do + @socket = @addr.connect_to(@to_addr) + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + @addr.connect_to(@to_addr) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'treats the last argument as a set of options if it is a Hash' do + @socket = @addr.connect_to(@to_addr, timeout: 2) + @socket.should be_an_instance_of(Socket) + end + + it 'binds the socket to the local address' do + @socket = @addr.connect_to(@to_addr) + + @socket.local_address.ip_address.should == ip_address + + @socket.local_address.ip_port.should > 0 + @socket.local_address.ip_port.should_not == @port + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb new file mode 100644 index 0000000000..3c2f9f73d8 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb @@ -0,0 +1,113 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#family_addrinfo' do + it 'raises ArgumentError if no arguments are given' do + addr = Addrinfo.tcp('127.0.0.1', 0) + + -> { addr.family_addrinfo }.should raise_error(ArgumentError) + end + + describe 'using multiple arguments' do + describe 'with an IP Addrinfo' do + before do + @source = Addrinfo.tcp('127.0.0.1', 0) + end + + it 'raises ArgumentError if only 1 argument is given' do + -> { @source.family_addrinfo('127.0.0.1') }.should raise_error(ArgumentError) + end + + it 'raises ArgumentError if more than 2 arguments are given' do + -> { @source.family_addrinfo('127.0.0.1', 0, 666) }.should raise_error(ArgumentError) + end + + it 'returns an Addrinfo when a host and port are given' do + addr = @source.family_addrinfo('127.0.0.1', 0) + + addr.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + @addr = @source.family_addrinfo('127.0.0.1', 0) + end + + it 'uses the same address family as the source Addrinfo' do + @addr.afamily.should == @source.afamily + end + + it 'uses the same protocol family as the source Addrinfo' do + @addr.pfamily.should == @source.pfamily + end + + it 'uses the same socket type as the source Addrinfo' do + @addr.socktype.should == @source.socktype + end + + it 'uses the same protocol as the source Addrinfo' do + @addr.protocol.should == @source.protocol + end + end + end + + describe 'with a UNIX Addrinfo' do + before do + @source = Addrinfo.unix('cats') + end + + it 'raises ArgumentError if more than 1 argument is given' do + -> { @source.family_addrinfo('foo', 'bar') }.should raise_error(ArgumentError) + end + + it 'returns an Addrinfo when a UNIX socket path is given' do + addr = @source.family_addrinfo('dogs') + + addr.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + @addr = @source.family_addrinfo('dogs') + end + + it 'uses AF_UNIX as the address family' do + @addr.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @addr.pfamily.should == Socket::PF_UNIX + end + + it 'uses the given socket path' do + @addr.unix_path.should == 'dogs' + end + end + end + end + + describe 'using an Addrinfo as the 1st argument' do + before do + @source = Addrinfo.tcp('127.0.0.1', 0) + end + + it 'returns the input Addrinfo' do + input = Addrinfo.tcp('127.0.0.2', 0) + @source.family_addrinfo(input).should == input + end + + it 'raises ArgumentError if more than 1 argument is given' do + input = Addrinfo.tcp('127.0.0.2', 0) + -> { @source.family_addrinfo(input, 666) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if the protocol families don't match" do + input = Addrinfo.tcp('::1', 0) + -> { @source.family_addrinfo(input) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if the socket types don't match" do + input = Addrinfo.udp('127.0.0.1', 0) + -> { @source.family_addrinfo(input) }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/foreach_spec.rb b/spec/ruby/library/socket/addrinfo/foreach_spec.rb new file mode 100644 index 0000000000..6ec8fab905 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/foreach_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +describe 'Addrinfo.foreach' do + it 'yields Addrinfo instances to the supplied block' do + Addrinfo.foreach('127.0.0.1', 80) do |addr| + addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb new file mode 100644 index 0000000000..e05fe9967a --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb @@ -0,0 +1,87 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo.getaddrinfo' do + it 'returns an Array of Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80) + + array.should be_an_instance_of(Array) + array[0].should be_an_instance_of(Addrinfo) + end + + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'sets the IP address of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].ip_address.should == ip_address + end + + it 'sets the port of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].ip_port.should == 80 + end + + it 'sets the address family of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].afamily.should == family + end + + it 'sets the protocol family of the Addrinfo instances' do + array = Addrinfo.getaddrinfo(ip_address, 80) + + array[0].pfamily.should == family + end + end + + guard -> { SocketSpecs.ipv6_available? } do + it 'sets a custom protocol family of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('::1', 80, Socket::PF_INET6) + + array[0].pfamily.should == Socket::PF_INET6 + end + + it 'sets a corresponding address family based on a custom protocol family' do + array = Addrinfo.getaddrinfo('::1', 80, Socket::PF_INET6) + + array[0].afamily.should == Socket::AF_INET6 + end + end + + platform_is_not :windows do + it 'sets the default socket type of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80) + possible = [Socket::SOCK_STREAM, Socket::SOCK_DGRAM] + + possible.should include(array[0].socktype) + end + end + + it 'sets a custom socket type of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80, nil, Socket::SOCK_DGRAM) + + array[0].socktype.should == Socket::SOCK_DGRAM + end + + platform_is_not :windows do + it 'sets the default socket protocol of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80) + possible = [Socket::IPPROTO_TCP, Socket::IPPROTO_UDP] + + possible.should include(array[0].protocol) + end + end + + it 'sets a custom socket protocol of the Addrinfo instances' do + array = Addrinfo.getaddrinfo('127.0.0.1', 80, nil, nil, Socket::IPPROTO_UDP) + + array[0].protocol.should == Socket::IPPROTO_UDP + end + + it 'sets the canonical name when AI_CANONNAME is given as a flag' do + array = Addrinfo.getaddrinfo('localhost', 80, nil, nil, nil, Socket::AI_CANONNAME) + + array[0].canonname.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb new file mode 100644 index 0000000000..43b5a2000a --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb @@ -0,0 +1,40 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo#getnameinfo' do + describe 'using an IP Addrinfo' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @addr = Addrinfo.tcp(ip_address, 21) + end + + it 'returns the node and service names' do + host, service = @addr.getnameinfo + service.should == 'ftp' + end + + it 'accepts flags as an Integer as the first argument' do + host, service = @addr.getnameinfo(Socket::NI_NUMERICSERV) + service.should == '21' + end + end + end + + platform_is :linux do + platform_is_not :android do + describe 'using a UNIX Addrinfo' do + before do + @addr = Addrinfo.unix('cats') + @host = Socket.gethostname + end + + it 'returns the hostname and UNIX socket path' do + host, path = @addr.getnameinfo + + host.should == @host + path.should == 'cats' + end + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/initialize_spec.rb b/spec/ruby/library/socket/addrinfo/initialize_spec.rb new file mode 100644 index 0000000000..1f16531aaa --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/initialize_spec.rb @@ -0,0 +1,589 @@ +require_relative '../spec_helper' + +describe "Addrinfo#initialize" do + + describe "with a sockaddr string" do + + describe "without a family" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1")) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the UNSPEC pfamily" do + @addrinfo.pfamily.should == Socket::PF_UNSPEC + end + + it 'returns AF_INET as the default address family' do + addr = Addrinfo.new(Socket.sockaddr_in(80, '127.0.0.1')) + + addr.afamily.should == Socket::AF_INET + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family given" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the INET6 pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family and socket type" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6, Socket::SOCK_STREAM) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the INET6 pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the specified socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family, socket type and protocol" do + before :each do + @addrinfo = Addrinfo.new(Socket.sockaddr_in("smtp", "2001:DB8::1"), Socket::PF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "2001:db8::1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 25 + end + + it "returns the INET6 pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + + it "returns the INET6 afamily" do + @addrinfo.afamily.should == Socket::AF_INET6 + end + + it "returns the specified socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the specified protocol" do + @addrinfo.protocol.should == Socket::IPPROTO_TCP + end + end + + end + + describe "with a sockaddr array" do + + describe "without a family" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"]) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe 'with a valid IP address' do + # Uses AF_INET6 since AF_INET is the default, making it a better test + # that Addrinfo actually sets the family correctly. + before do + @sockaddr = ['AF_INET6', 80, 'hostname', '::1'] + end + + it 'returns an Addrinfo with the correct IP' do + addr = Addrinfo.new(@sockaddr) + + addr.ip_address.should == '::1' + end + + it 'returns an Addrinfo with the correct address family' do + addr = Addrinfo.new(@sockaddr) + + addr.afamily.should == Socket::AF_INET6 + end + + it 'returns an Addrinfo with the correct protocol family' do + addr = Addrinfo.new(@sockaddr) + + addr.pfamily.should == Socket::PF_INET6 + end + + it 'returns an Addrinfo with the correct port' do + addr = Addrinfo.new(@sockaddr) + + addr.ip_port.should == 80 + end + end + + describe 'with an invalid IP address' do + it 'raises SocketError' do + block = -> { Addrinfo.new(['AF_INET6', 80, 'hostname', '127.0.0.1']) } + + block.should raise_error(SocketError) + end + end + + describe "with a family given" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == 0 + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + end + + describe "with a family and socket type" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET, Socket::SOCK_STREAM) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the 0 protocol" do + @addrinfo.protocol.should == 0 + end + + [:SOCK_STREAM, :SOCK_DGRAM, :SOCK_RAW].each do |type| + it "overwrites the socket type #{type}" do + sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + + value = Socket.const_get(type) + addr = Addrinfo.new(sockaddr, nil, value) + + addr.socktype.should == value + end + end + + platform_is_not :android do + with_feature :sock_packet do + [:SOCK_SEQPACKET].each do |type| + it "overwrites the socket type #{type}" do + sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + + value = Socket.const_get(type) + addr = Addrinfo.new(sockaddr, nil, value) + + addr.socktype.should == value + end + end + end + end + + it "raises SocketError when using SOCK_RDM" do + sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + value = Socket::SOCK_RDM + block = -> { Addrinfo.new(sockaddr, nil, value) } + + block.should raise_error(SocketError) + end + end + + describe "with a family, socket type and protocol" do + before :each do + @addrinfo = Addrinfo.new(["AF_INET", 46102, "localhost", "127.0.0.1"], Socket::PF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP) + end + + it "stores the ip address from the sockaddr" do + @addrinfo.ip_address.should == "127.0.0.1" + end + + it "stores the port number from the sockaddr" do + @addrinfo.ip_port.should == 46102 + end + + it "returns the INET pfamily" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + it "returns the INET afamily" do + @addrinfo.afamily.should == Socket::AF_INET + end + + it "returns the 0 socket type" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + + it "returns the specified protocol" do + @addrinfo.protocol.should == Socket::IPPROTO_TCP + end + end + end + + describe 'using an Array with extra arguments' do + describe 'with the AF_INET6 address family and an explicit protocol family' do + before do + @sockaddr = ['AF_INET6', 80, 'hostname', '127.0.0.1'] + end + + it "raises SocketError when using any Socket constant except AF_INET(6)/PF_INET(6)" do + Socket.constants.grep(/(^AF_|^PF_)(?!INET)/).each do |constant| + value = Socket.const_get(constant) + -> { + Addrinfo.new(@sockaddr, value) + }.should raise_error(SocketError) + end + end + end + + describe 'with the AF_INET address family and an explicit socket protocol' do + before do + @sockaddr = ['AF_INET', 80, 'hostname', '127.0.0.1'] + end + + describe 'and no socket type is given' do + valid = [:IPPROTO_IP, :IPPROTO_UDP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, nil, value) + + addr.protocol.should == value + end + end + + platform_is_not :windows, :aix do + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, nil, value) } + + block.should raise_error(SocketError) + end + end + end + end + + describe 'and the socket type is set to SOCK_DGRAM' do + before do + @socktype = Socket::SOCK_DGRAM + end + + valid = [:IPPROTO_IP, :IPPROTO_UDP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + + platform_is_not :windows, :aix do + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + + with_feature :sock_packet do + describe 'and the socket type is set to SOCK_PACKET' do + before do + @socktype = Socket::SOCK_PACKET + end + + Socket.constants.grep(/^IPPROTO/).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + + describe 'and the socket type is set to SOCK_RAW' do + before do + @socktype = Socket::SOCK_RAW + end + + Socket.constants.grep(/^IPPROTO/).each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + end + + describe 'and the socket type is set to SOCK_RDM' do + before do + @socktype = Socket::SOCK_RDM + end + + Socket.constants.grep(/^IPPROTO/).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + + platform_is :linux do + platform_is_not :android do + describe 'and the socket type is set to SOCK_SEQPACKET' do + before do + @socktype = Socket::SOCK_SEQPACKET + end + + valid = [:IPPROTO_IP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + end + + describe 'and the socket type is set to SOCK_STREAM' do + before do + @socktype = Socket::SOCK_STREAM + end + + valid = [:IPPROTO_IP, :IPPROTO_TCP, :IPPROTO_HOPOPTS] + + valid.each do |type| + it "overwrites the protocol when using #{type}" do + value = Socket.const_get(type) + addr = Addrinfo.new(@sockaddr, nil, @socktype, value) + + addr.protocol.should == value + end + end + + platform_is_not :windows, :aix do + (Socket.constants.grep(/^IPPROTO/) - valid).each do |type| + it "raises SocketError when using #{type}" do + value = Socket.const_get(type) + block = -> { Addrinfo.new(@sockaddr, nil, @socktype, value) } + + block.should raise_error(SocketError) + end + end + end + end + end + end + + describe 'with Symbols' do + before do + @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') + end + + it 'returns an Addrinfo with :PF_INET family' do + addr = Addrinfo.new(@sockaddr, :PF_INET) + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with :INET family' do + addr = Addrinfo.new(@sockaddr, :INET) + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with :SOCK_STREAM as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, :SOCK_STREAM) + + addr.socktype.should == Socket::SOCK_STREAM + end + + it 'returns an Addrinfo with :STREAM as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, :STREAM) + + addr.socktype.should == Socket::SOCK_STREAM + end + end + + describe 'with Strings' do + before do + @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') + end + + it 'returns an Addrinfo with "PF_INET" family' do + addr = Addrinfo.new(@sockaddr, 'PF_INET') + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with "INET" family' do + addr = Addrinfo.new(@sockaddr, 'INET') + + addr.pfamily.should == Socket::PF_INET + end + + it 'returns an Addrinfo with "SOCK_STREAM" as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, 'SOCK_STREAM') + + addr.socktype.should == Socket::SOCK_STREAM + end + + it 'returns an Addrinfo with "STREAM" as the socket type' do + addr = Addrinfo.new(@sockaddr, nil, 'STREAM') + + addr.socktype.should == Socket::SOCK_STREAM + end + end + + describe 'using separate arguments for a Unix socket' do + before do + @sockaddr = Socket.pack_sockaddr_un('socket') + end + + it 'returns an Addrinfo with the correct unix path' do + Addrinfo.new(@sockaddr).unix_path.should == 'socket' + end + + it 'returns an Addrinfo with the correct protocol family' do + Addrinfo.new(@sockaddr).pfamily.should == Socket::PF_UNSPEC + end + + it 'returns an Addrinfo with the correct address family' do + Addrinfo.new(@sockaddr).afamily.should == Socket::AF_UNIX + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb new file mode 100644 index 0000000000..6b18c79469 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb @@ -0,0 +1,48 @@ +require_relative '../spec_helper' + + +describe 'Addrinfo#inspect_sockaddr' do + describe 'using an IPv4 address' do + it 'returns a String containing the IP address and port number' do + addr = Addrinfo.tcp('127.0.0.1', 80) + + addr.inspect_sockaddr.should == '127.0.0.1:80' + end + + it 'returns a String containing just the IP address when no port is given' do + addr = Addrinfo.tcp('127.0.0.1', 0) + + addr.inspect_sockaddr.should == '127.0.0.1' + end + end + + describe 'using an IPv6 address' do + before :each do + @ip = '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + end + + it 'returns a String containing the IP address and port number' do + Addrinfo.tcp('::1', 80).inspect_sockaddr.should == '[::1]:80' + Addrinfo.tcp(@ip, 80).inspect_sockaddr.should == '[2001:db8:85a3::8a2e:370:7334]:80' + end + + it 'returns a String containing just the IP address when no port is given' do + Addrinfo.tcp('::1', 0).inspect_sockaddr.should == '::1' + Addrinfo.tcp(@ip, 0).inspect_sockaddr.should == '2001:db8:85a3::8a2e:370:7334' + end + end + + describe 'using a UNIX path' do + it 'returns a String containing the UNIX path' do + addr = Addrinfo.unix('/foo/bar') + + addr.inspect_sockaddr.should == '/foo/bar' + end + + it 'returns a String containing the UNIX path when using a relative path' do + addr = Addrinfo.unix('foo') + + addr.inspect_sockaddr.should == 'UNIX foo' + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/inspect_spec.rb b/spec/ruby/library/socket/addrinfo/inspect_spec.rb new file mode 100644 index 0000000000..1442af6162 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/inspect_spec.rb @@ -0,0 +1,63 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#inspect' do + describe 'using an IPv4 Addrinfo' do + it 'returns a String when using a TCP Addrinfo' do + addr = Addrinfo.tcp('127.0.0.1', 80) + + addr.inspect.should == '#<Addrinfo: 127.0.0.1:80 TCP>' + end + + it 'returns a String when using an UDP Addrinfo' do + addr = Addrinfo.udp('127.0.0.1', 80) + + addr.inspect.should == '#<Addrinfo: 127.0.0.1:80 UDP>' + end + + it 'returns a String when using an Addrinfo without a port' do + addr = Addrinfo.ip('127.0.0.1') + + addr.inspect.should == '#<Addrinfo: 127.0.0.1>' + end + end + + describe 'using an IPv6 Addrinfo' do + it 'returns a String when using a TCP Addrinfo' do + addr = Addrinfo.tcp('::1', 80) + + addr.inspect.should == '#<Addrinfo: [::1]:80 TCP>' + end + + it 'returns a String when using an UDP Addrinfo' do + addr = Addrinfo.udp('::1', 80) + + addr.inspect.should == '#<Addrinfo: [::1]:80 UDP>' + end + + it 'returns a String when using an Addrinfo without a port' do + addr = Addrinfo.ip('::1') + + addr.inspect.should == '#<Addrinfo: ::1>' + end + end + + describe 'using a UNIX Addrinfo' do + it 'returns a String' do + addr = Addrinfo.unix('/foo') + + addr.inspect.should == '#<Addrinfo: /foo SOCK_STREAM>' + end + + it 'returns a String when using a relative UNIX path' do + addr = Addrinfo.unix('foo') + + addr.inspect.should == '#<Addrinfo: UNIX foo SOCK_STREAM>' + end + + it 'returns a String when using a DGRAM socket' do + addr = Addrinfo.unix('/foo', Socket::SOCK_DGRAM) + + addr.inspect.should == '#<Addrinfo: /foo SOCK_DGRAM>' + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_address_spec.rb b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb new file mode 100644 index 0000000000..193432e861 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_address_spec.rb @@ -0,0 +1,64 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ip_address" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns the ip address" do + @addrinfo.ip_address.should == "127.0.0.1" + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns the ip address" do + @addrinfo.ip_address.should == "::1" + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "raises an exception" do + -> { @addrinfo.ip_address }.should raise_error(SocketError) + end + end + + describe 'with an Array as the socket address' do + it 'returns the IP as a String' do + sockaddr = ['AF_INET', 80, 'localhost', '127.0.0.1'] + addr = Addrinfo.new(sockaddr) + + addr.ip_address.should == '127.0.0.1' + end + end + + describe 'without an IP address' do + before do + @ips = ['127.0.0.1', '0.0.0.0', '::1'] + end + + # Both these cases seem to return different values at times on MRI. Since + # this is network dependent we can't rely on an exact IP being returned. + it 'returns the local IP address when using an empty String as the IP' do + sockaddr = Socket.sockaddr_in(80, '') + addr = Addrinfo.new(sockaddr) + + @ips.include?(addr.ip_address).should == true + end + + it 'returns the local IP address when using nil as the IP' do + sockaddr = Socket.sockaddr_in(80, nil) + addr = Addrinfo.new(sockaddr) + + @ips.include?(addr.ip_address).should == true + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_port_spec.rb b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb new file mode 100644 index 0000000000..f10ce35143 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_port_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ip_port" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns the port" do + @addrinfo.ip_port.should == 80 + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns the port" do + @addrinfo.ip_port.should == 80 + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "raises an exception" do + -> { @addrinfo.ip_port }.should raise_error(SocketError) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_spec.rb b/spec/ruby/library/socket/addrinfo/ip_spec.rb new file mode 100644 index 0000000000..09b9341605 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_spec.rb @@ -0,0 +1,62 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Addrinfo#ip?" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns true" do + @addrinfo.ip?.should be_true + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns true" do + @addrinfo.ip?.should be_true + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ip?.should be_false + end + end +end + +describe 'Addrinfo.ip' do + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'returns an Addrinfo instance' do + Addrinfo.ip(ip_address).should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.ip(ip_address).ip_address.should == ip_address + end + + it 'sets the port to 0' do + Addrinfo.ip(ip_address).ip_port.should == 0 + end + + it 'sets the address family' do + Addrinfo.ip(ip_address).afamily.should == family + end + + it 'sets the protocol family' do + Addrinfo.ip(ip_address).pfamily.should == family + end + + it 'sets the socket type to 0' do + Addrinfo.ip(ip_address).socktype.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb new file mode 100644 index 0000000000..58260c4557 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ip_unpack" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns the ip address and port pair" do + @addrinfo.ip_unpack.should == ["127.0.0.1", 80] + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns the ip address and port pair" do + @addrinfo.ip_unpack.should == ["::1", 80] + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "raises an exception" do + -> { @addrinfo.ip_unpack }.should raise_error(SocketError) + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb new file mode 100644 index 0000000000..3a584d4f52 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb @@ -0,0 +1,41 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4_loopback?" do + describe "for an ipv4 socket" do + it "returns true for the loopback address" do + Addrinfo.ip('127.0.0.1').should.ipv4_loopback? + Addrinfo.ip('127.0.0.2').should.ipv4_loopback? + Addrinfo.ip('127.255.0.1').should.ipv4_loopback? + Addrinfo.ip('127.255.255.255').should.ipv4_loopback? + end + + it "returns false for another address" do + Addrinfo.ip('255.255.255.0').ipv4_loopback?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @loopback = Addrinfo.tcp("::1", 80) + @other = Addrinfo.tcp("::", 80) + end + + it "returns false for the loopback address" do + @loopback.ipv4_loopback?.should be_false + end + + it "returns false for another address" do + @other.ipv4_loopback?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_loopback?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb new file mode 100644 index 0000000000..e4b4cfcc84 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb @@ -0,0 +1,27 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4_multicast?" do + it 'returns true for a multicast address' do + Addrinfo.ip('224.0.0.0').should.ipv4_multicast? + Addrinfo.ip('224.0.0.9').should.ipv4_multicast? + Addrinfo.ip('239.255.255.250').should.ipv4_multicast? + end + + it 'returns false for a regular address' do + Addrinfo.ip('8.8.8.8').should_not.ipv4_multicast? + end + + it 'returns false for an IPv6 address' do + Addrinfo.ip('::1').should_not.ipv4_multicast? + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_multicast?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb new file mode 100644 index 0000000000..97218b5ba3 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb @@ -0,0 +1,45 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4_private?" do + describe "for an ipv4 socket" do + before :each do + @private = Addrinfo.tcp("10.0.0.1", 80) + @other = Addrinfo.tcp("0.0.0.0", 80) + end + + it "returns true for a private address" do + Addrinfo.ip('10.0.0.0').should.ipv4_private? + Addrinfo.ip('10.0.0.5').should.ipv4_private? + + Addrinfo.ip('172.16.0.0').should.ipv4_private? + Addrinfo.ip('172.16.0.5').should.ipv4_private? + + Addrinfo.ip('192.168.0.0').should.ipv4_private? + Addrinfo.ip('192.168.0.5').should.ipv4_private? + end + + it "returns false for a public address" do + @other.ipv4_private?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @other = Addrinfo.tcp("::", 80) + end + + it "returns false" do + @other.ipv4_private?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4_private?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb new file mode 100644 index 0000000000..61f7759b10 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv4_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv4?" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("10.0.0.1", 80) + end + + it "returns true" do + @addrinfo.ipv4?.should be_true + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns false" do + @addrinfo.ipv4?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv4?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb new file mode 100644 index 0000000000..bfef396381 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Addrinfo#ipv6_linklocal?' do + platform_is_not :aix do + it 'returns true for a link-local address' do + Addrinfo.ip('fe80::').should.ipv6_linklocal? + Addrinfo.ip('fe81::').should.ipv6_linklocal? + Addrinfo.ip('fe8f::').should.ipv6_linklocal? + Addrinfo.ip('fe80::1').should.ipv6_linklocal? + end + end + + it 'returns false for a regular address' do + Addrinfo.ip('::1').should_not.ipv6_linklocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_linklocal? + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb new file mode 100644 index 0000000000..ffc75185ea --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv6_loopback?" do + describe "for an ipv4 socket" do + before :each do + @loopback = Addrinfo.tcp("127.0.0.1", 80) + @other = Addrinfo.tcp("0.0.0.0", 80) + end + + it "returns false for the loopback address" do + @loopback.ipv6_loopback?.should be_false + end + + it "returns false for another address" do + @other.ipv6_loopback?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @loopback = Addrinfo.tcp("::1", 80) + @other = Addrinfo.tcp("::", 80) + end + + it "returns true for the loopback address" do + @loopback.ipv6_loopback?.should be_true + end + + it "returns false for another address" do + @other.ipv6_loopback?.should be_false + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6_loopback?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb new file mode 100644 index 0000000000..01fa0992ba --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_global?' do + it 'returns true for a multi-cast address in the global scope' do + Addrinfo.ip('ff1e::').should.ipv6_mc_global? + Addrinfo.ip('fffe::').should.ipv6_mc_global? + Addrinfo.ip('ff0e::').should.ipv6_mc_global? + Addrinfo.ip('ff1e::1').should.ipv6_mc_global? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_global? + Addrinfo.ip('ff1a::').should_not.ipv6_mc_global? + Addrinfo.ip('ff1f::1').should_not.ipv6_mc_global? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_global? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb new file mode 100644 index 0000000000..a1298919eb --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb @@ -0,0 +1,19 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_linklocal?' do + it 'returns true for a multi-cast link-local address' do + Addrinfo.ip('ff12::').should.ipv6_mc_linklocal? + Addrinfo.ip('ff02::').should.ipv6_mc_linklocal? + Addrinfo.ip('fff2::').should.ipv6_mc_linklocal? + Addrinfo.ip('ff12::1').should.ipv6_mc_linklocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_linklocal? + Addrinfo.ip('fff1::').should_not.ipv6_mc_linklocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_linklocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb new file mode 100644 index 0000000000..0aee952d88 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_nodelocal?' do + it 'returns true for a multi-cast node-local address' do + Addrinfo.ip('ff11::').should.ipv6_mc_nodelocal? + Addrinfo.ip('ff01::').should.ipv6_mc_nodelocal? + Addrinfo.ip('fff1::').should.ipv6_mc_nodelocal? + Addrinfo.ip('ff11::1').should.ipv6_mc_nodelocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_nodelocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_nodelocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb new file mode 100644 index 0000000000..2977a98d30 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_orglocal?' do + it 'returns true for a multi-cast org-local address' do + Addrinfo.ip('ff18::').should.ipv6_mc_orglocal? + Addrinfo.ip('ff08::').should.ipv6_mc_orglocal? + Addrinfo.ip('fff8::').should.ipv6_mc_orglocal? + Addrinfo.ip('ff18::1').should.ipv6_mc_orglocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_orglocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_orglocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb new file mode 100644 index 0000000000..58e5976a40 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_mc_sitelocal?' do + it 'returns true for a multi-cast site-local address' do + Addrinfo.ip('ff15::').should.ipv6_mc_sitelocal? + Addrinfo.ip('ff05::').should.ipv6_mc_sitelocal? + Addrinfo.ip('fff5::').should.ipv6_mc_sitelocal? + Addrinfo.ip('ff15::1').should.ipv6_mc_sitelocal? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_mc_sitelocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_mc_sitelocal? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb new file mode 100644 index 0000000000..99d4e8cf4d --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb @@ -0,0 +1,46 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv6_multicast?" do + describe "for an ipv4 socket" do + before :each do + @multicast = Addrinfo.tcp("224.0.0.1", 80) + @other = Addrinfo.tcp("0.0.0.0", 80) + end + + it "returns true for a multicast address" do + @multicast.ipv6_multicast?.should be_false + end + + it "returns false for another address" do + @other.ipv6_multicast?.should be_false + end + end + + describe "for an ipv6 socket" do + it "returns true for a multicast address" do + Addrinfo.ip('ff00::').should.ipv6_multicast? + Addrinfo.ip('ff00::1').should.ipv6_multicast? + Addrinfo.ip('ff08::1').should.ipv6_multicast? + Addrinfo.ip('fff8::1').should.ipv6_multicast? + + Addrinfo.ip('ff02::').should.ipv6_multicast? + Addrinfo.ip('ff02::1').should.ipv6_multicast? + Addrinfo.ip('ff0f::').should.ipv6_multicast? + end + + it "returns false for another address" do + Addrinfo.ip('::1').should_not.ipv6_multicast? + Addrinfo.ip('fe80::').should_not.ipv6_multicast? + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6_multicast?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb new file mode 100644 index 0000000000..9158eb5809 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Addrinfo#ipv6_sitelocal?' do + platform_is_not :aix do + it 'returns true for a site-local address' do + Addrinfo.ip('feef::').should.ipv6_sitelocal? + Addrinfo.ip('fee0::').should.ipv6_sitelocal? + Addrinfo.ip('fee2::').should.ipv6_sitelocal? + Addrinfo.ip('feef::1').should.ipv6_sitelocal? + end + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_sitelocal? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_sitelocal? + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb new file mode 100644 index 0000000000..436d5e930b --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe "Addrinfo#ipv6?" do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("10.0.0.1", 80) + end + + it "returns true" do + @addrinfo.ipv6?.should be_false + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns false" do + @addrinfo.ipv6?.should be_true + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns false" do + @addrinfo.ipv6?.should be_false + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb new file mode 100644 index 0000000000..29050bec20 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb @@ -0,0 +1,71 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Addrinfo#ipv6_to_ipv4' do + it 'returns an Addrinfo for ::192.168.1.1' do + addr = Addrinfo.ip('::192.168.1.1').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '192.168.1.1' + end + + platform_is_not :aix do + it 'returns an Addrinfo for ::0.0.1.1' do + addr = Addrinfo.ip('::0.0.1.1').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '0.0.1.1' + end + + it 'returns an Addrinfo for ::0.0.1.0' do + addr = Addrinfo.ip('::0.0.1.0').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '0.0.1.0' + end + + it 'returns an Addrinfo for ::0.1.0.0' do + addr = Addrinfo.ip('::0.1.0.0').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '0.1.0.0' + end + end + + it 'returns an Addrinfo for ::ffff:192.168.1.1' do + addr = Addrinfo.ip('::ffff:192.168.1.1').ipv6_to_ipv4 + + addr.should be_an_instance_of(Addrinfo) + + addr.afamily.should == Socket::AF_INET + addr.ip_address.should == '192.168.1.1' + end + + it 'returns nil for ::0.0.0.1' do + Addrinfo.ip('::0.0.0.1').ipv6_to_ipv4.should be_nil + end + + it 'returns nil for a pure IPv6 Addrinfo' do + Addrinfo.ip('::1').ipv6_to_ipv4.should be_nil + end + + it 'returns nil for an IPv4 Addrinfo' do + Addrinfo.ip('192.168.1.1').ipv6_to_ipv4.should be_nil + end + + describe 'for a unix socket' do + it 'returns nil for a UNIX Addrinfo' do + Addrinfo.unix('foo').ipv6_to_ipv4.should be_nil + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb new file mode 100644 index 0000000000..22f0fa3b75 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_unique_local?' do + it 'returns true for an unique local IPv6 address' do + Addrinfo.ip('fc00::').should.ipv6_unique_local? + Addrinfo.ip('fd00::').should.ipv6_unique_local? + Addrinfo.ip('fcff::').should.ipv6_unique_local? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_unique_local? + Addrinfo.ip('fe00::').should_not.ipv6_unique_local? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_unique_local? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb new file mode 100644 index 0000000000..d63979ceda --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb @@ -0,0 +1,15 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_unspecified?' do + it 'returns true for an unspecified IPv6 address' do + Addrinfo.ip('::').should.ipv6_unspecified? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_unspecified? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_unspecified? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb new file mode 100644 index 0000000000..21ca85af99 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_v4compat?' do + it 'returns true for an IPv4 compatible address' do + Addrinfo.ip('::127.0.0.1').should.ipv6_v4compat? + Addrinfo.ip('::192.168.1.1').should.ipv6_v4compat? + end + + it 'returns false for an IPv4 mapped address' do + Addrinfo.ip('::ffff:192.168.1.1').should_not.ipv6_v4compat? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_v4compat? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_v4compat? + end +end diff --git a/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb b/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb new file mode 100644 index 0000000000..7dac0e75db --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#ipv6_v4mapped?' do + it 'returns true for an IPv4 compatible address' do + Addrinfo.ip('::ffff:192.168.1.1').should.ipv6_v4mapped? + end + + it 'returns false for an IPv4 compatible address' do + Addrinfo.ip('::192.168.1.1').should_not.ipv6_v4mapped? + Addrinfo.ip('::127.0.0.1').should_not.ipv6_v4mapped? + end + + it 'returns false for a regular IPv6 address' do + Addrinfo.ip('::1').should_not.ipv6_v4mapped? + end + + it 'returns false for an IPv4 address' do + Addrinfo.ip('127.0.0.1').should_not.ipv6_v4mapped? + end +end diff --git a/spec/ruby/library/socket/addrinfo/listen_spec.rb b/spec/ruby/library/socket/addrinfo/listen_spec.rb new file mode 100644 index 0000000000..931093f732 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/listen_spec.rb @@ -0,0 +1,34 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#listen' do + before do + @addr = Addrinfo.tcp('127.0.0.1', 0) + @socket = nil + end + + after do + @socket.close if @socket + end + + it 'returns a Socket when no block is given' do + @socket = @addr.listen + + @socket.should be_an_instance_of(Socket) + end + + it 'yields the Socket if a block is given' do + @addr.listen do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'closes the socket if a block is given' do + socket = nil + + @addr.listen do |sock| + socket = sock + end + + socket.should.closed? + end +end diff --git a/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb new file mode 100644 index 0000000000..e2c3497f7f --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb @@ -0,0 +1,80 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#marshal_dump' do + describe 'using an IP Addrinfo' do + before do + @addr = Addrinfo.getaddrinfo('localhost', 80, :INET, :STREAM, + Socket::IPPROTO_TCP, Socket::AI_CANONNAME)[0] + end + + it 'returns an Array' do + @addr.marshal_dump.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @addr.marshal_dump + end + + it 'includes the address family as the 1st value' do + @array[0].should == 'AF_INET' + end + + it 'includes the IP address as the 2nd value' do + @array[1].should == [@addr.ip_address, @addr.ip_port.to_s] + end + + it 'includes the protocol family as the 3rd value' do + @array[2].should == 'PF_INET' + end + + it 'includes the socket type as the 4th value' do + @array[3].should == 'SOCK_STREAM' + end + + it 'includes the protocol as the 5th value' do + @array[4].should == 'IPPROTO_TCP' + end + + it 'includes the canonical name as the 6th value' do + @array[5].should == @addr.canonname + end + end + end + + describe 'using a UNIX Addrinfo' do + before do + @addr = Addrinfo.unix('foo') + end + + it 'returns an Array' do + @addr.marshal_dump.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @addr.marshal_dump + end + + it 'includes the address family as the 1st value' do + @array[0].should == 'AF_UNIX' + end + + it 'includes the UNIX path as the 2nd value' do + @array[1].should == @addr.unix_path + end + + it 'includes the protocol family as the 3rd value' do + @array[2].should == 'PF_UNIX' + end + + it 'includes the socket type as the 4th value' do + @array[3].should == 'SOCK_STREAM' + end + + it 'includes the protocol as the 5th value' do + @array[4].should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb new file mode 100644 index 0000000000..02cef90115 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/marshal_load_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +describe 'Addrinfo#marshal_load' do + describe 'using an IP address' do + it 'returns a new Addrinfo' do + source = Addrinfo.getaddrinfo('localhost', 80, :INET, :STREAM, + Socket::IPPROTO_TCP, Socket::AI_CANONNAME)[0] + + addr = Marshal.load(Marshal.dump(source)) + + addr.afamily.should == source.afamily + addr.pfamily.should == source.pfamily + addr.socktype.should == source.socktype + addr.protocol.should == source.protocol + addr.ip_address.should == source.ip_address + addr.ip_port.should == source.ip_port + addr.canonname.should == source.canonname + end + end + + describe 'using a UNIX socket' do + it 'returns a new Addrinfo' do + source = Addrinfo.unix('foo') + addr = Marshal.load(Marshal.dump(source)) + + addr.afamily.should == source.afamily + addr.pfamily.should == source.pfamily + addr.socktype.should == source.socktype + addr.protocol.should == source.protocol + addr.unix_path.should == source.unix_path + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/pfamily_spec.rb b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb new file mode 100644 index 0000000000..da530b7fdc --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/pfamily_spec.rb @@ -0,0 +1,41 @@ +require_relative '../spec_helper' + +describe "Addrinfo#pfamily" do + it 'returns PF_UNSPEC as the default socket family' do + sockaddr = Socket.pack_sockaddr_in(80, 'localhost') + + Addrinfo.new(sockaddr).pfamily.should == Socket::PF_UNSPEC + end + + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns Socket::PF_INET" do + @addrinfo.pfamily.should == Socket::PF_INET + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns Socket::PF_INET6" do + @addrinfo.pfamily.should == Socket::PF_INET6 + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::PF_UNIX" do + @addrinfo.pfamily.should == Socket::PF_UNIX + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/protocol_spec.rb b/spec/ruby/library/socket/addrinfo/protocol_spec.rb new file mode 100644 index 0000000000..f6ffc9acf9 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/protocol_spec.rb @@ -0,0 +1,22 @@ +require_relative '../spec_helper' + +describe "Addrinfo#protocol" do + it 'returns 0 by default' do + Addrinfo.ip('127.0.0.1').protocol.should == 0 + end + + it 'returns a custom protocol when given' do + Addrinfo.tcp('127.0.0.1', 80).protocol.should == Socket::IPPROTO_TCP + Addrinfo.tcp('::1', 80).protocol.should == Socket::IPPROTO_TCP + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns 0" do + @addrinfo.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb new file mode 100644 index 0000000000..70d6bfbbfe --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb @@ -0,0 +1,47 @@ +describe :socket_addrinfo_to_sockaddr, shared: true do + describe "for an ipv4 socket" do + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_in(80, '127.0.0.1') + end + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_in(80, '::1') + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns a sockaddr packed structure" do + @addrinfo.send(@method).should == Socket.sockaddr_un('/tmp/sock') + end + end + + describe 'using a Addrinfo with just an IP address' do + it 'returns a String' do + addr = Addrinfo.ip('127.0.0.1') + + addr.send(@method).should == Socket.sockaddr_in(0, '127.0.0.1') + end + end + + describe 'using a Addrinfo without an IP and port' do + it 'returns a String' do + addr = Addrinfo.new(['AF_INET', 0, '', '']) + + addr.send(@method).should == Socket.sockaddr_in(0, '') + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/socktype_spec.rb b/spec/ruby/library/socket/addrinfo/socktype_spec.rb new file mode 100644 index 0000000000..e5f02cd759 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/socktype_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' + +describe "Addrinfo#socktype" do + it 'returns 0 by default' do + Addrinfo.ip('127.0.0.1').socktype.should == 0 + end + + it 'returns the socket type when given' do + Addrinfo.tcp('127.0.0.1', 80).socktype.should == Socket::SOCK_STREAM + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns Socket::SOCK_STREAM" do + @addrinfo.socktype.should == Socket::SOCK_STREAM + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/tcp_spec.rb b/spec/ruby/library/socket/addrinfo/tcp_spec.rb new file mode 100644 index 0000000000..c74c9c21c2 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/tcp_spec.rb @@ -0,0 +1,34 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo.tcp' do + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'returns an Addrinfo instance' do + Addrinfo.tcp(ip_address, 80).should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.tcp(ip_address, 80).ip_address.should == ip_address + end + + it 'sets the port' do + Addrinfo.tcp(ip_address, 80).ip_port.should == 80 + end + + it 'sets the address family' do + Addrinfo.tcp(ip_address, 80).afamily.should == family + end + + it 'sets the protocol family' do + Addrinfo.tcp(ip_address, 80).pfamily.should == family + end + + it 'sets the socket type' do + Addrinfo.tcp(ip_address, 80).socktype.should == Socket::SOCK_STREAM + end + + it 'sets the socket protocol' do + Addrinfo.tcp(ip_address, 80).protocol.should == Socket::IPPROTO_TCP + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/to_s_spec.rb b/spec/ruby/library/socket/addrinfo/to_s_spec.rb new file mode 100644 index 0000000000..ddf994e051 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../spec_helper' +require_relative 'shared/to_sockaddr' + +describe "Addrinfo#to_s" do + it_behaves_like :socket_addrinfo_to_sockaddr, :to_s +end diff --git a/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb b/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb new file mode 100644 index 0000000000..b9f75454bd --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb @@ -0,0 +1,6 @@ +require_relative '../spec_helper' +require_relative 'shared/to_sockaddr' + +describe "Addrinfo#to_sockaddr" do + it_behaves_like :socket_addrinfo_to_sockaddr, :to_sockaddr +end diff --git a/spec/ruby/library/socket/addrinfo/udp_spec.rb b/spec/ruby/library/socket/addrinfo/udp_spec.rb new file mode 100644 index 0000000000..ac02e76ef5 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/udp_spec.rb @@ -0,0 +1,34 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Addrinfo.udp' do + SocketSpecs.each_ip_protocol do |family, ip_address| + it 'returns an Addrinfo instance' do + Addrinfo.udp(ip_address, 80).should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.udp(ip_address, 80).ip_address.should == ip_address + end + + it 'sets the port' do + Addrinfo.udp(ip_address, 80).ip_port.should == 80 + end + + it 'sets the address family' do + Addrinfo.udp(ip_address, 80).afamily.should == family + end + + it 'sets the protocol family' do + Addrinfo.udp(ip_address, 80).pfamily.should == family + end + + it 'sets the socket type' do + Addrinfo.udp(ip_address, 80).socktype.should == Socket::SOCK_DGRAM + end + + it 'sets the socket protocol' do + Addrinfo.udp(ip_address, 80).protocol.should == Socket::IPPROTO_UDP + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/unix_path_spec.rb b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb new file mode 100644 index 0000000000..2a9076a354 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/unix_path_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +describe "Addrinfo#unix_path" do + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "raises an exception" do + -> { @addrinfo.unix_path }.should raise_error(SocketError) + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "raises an exception" do + -> { @addrinfo.unix_path }.should raise_error(SocketError) + end + end + + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns the socket path" do + @addrinfo.unix_path.should == "/tmp/sock" + end + end +end diff --git a/spec/ruby/library/socket/addrinfo/unix_spec.rb b/spec/ruby/library/socket/addrinfo/unix_spec.rb new file mode 100644 index 0000000000..7597533a76 --- /dev/null +++ b/spec/ruby/library/socket/addrinfo/unix_spec.rb @@ -0,0 +1,69 @@ +require_relative '../spec_helper' + +describe 'Addrinfo.unix' do + it 'returns an Addrinfo instance' do + Addrinfo.unix('socket').should be_an_instance_of(Addrinfo) + end + + it 'sets the IP address' do + Addrinfo.unix('socket').unix_path.should == 'socket' + end + + it 'sets the address family' do + Addrinfo.unix('socket').afamily.should == Socket::AF_UNIX + end + + it 'sets the protocol family' do + Addrinfo.unix('socket').pfamily.should == Socket::PF_UNIX + end + + it 'sets the socket type' do + Addrinfo.unix('socket').socktype.should == Socket::SOCK_STREAM + end + + it 'sets a custom socket type' do + addr = Addrinfo.unix('socket', Socket::SOCK_DGRAM) + + addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'sets the socket protocol to 0' do + Addrinfo.unix('socket').protocol.should == 0 + end +end + +describe "Addrinfo#unix?" do + describe "for an ipv4 socket" do + + before :each do + @addrinfo = Addrinfo.tcp("127.0.0.1", 80) + end + + it "returns false" do + @addrinfo.unix?.should be_false + end + + end + + describe "for an ipv6 socket" do + before :each do + @addrinfo = Addrinfo.tcp("::1", 80) + end + + it "returns false" do + @addrinfo.unix?.should be_false + end + end + + platform_is_not :windows do + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns true" do + @addrinfo.unix?.should be_true + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb b/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb new file mode 100644 index 0000000000..c54ee29825 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#cmsg_is?' do + describe 'using :INET, :IP, :TTL as the family, level, and type' do + before do + @data = Socket::AncillaryData.new(:INET, :IP, :TTL, '') + end + + it 'returns true when comparing with IPPROTO_IP and IP_TTL' do + @data.cmsg_is?(Socket::IPPROTO_IP, Socket::IP_TTL).should == true + end + + it 'returns true when comparing with :IP and :TTL' do + @data.cmsg_is?(:IP, :TTL).should == true + end + + with_feature :pktinfo do + it 'returns false when comparing with :IP and :PKTINFO' do + @data.cmsg_is?(:IP, :PKTINFO).should == false + end + end + + it 'returns false when comparing with :SOCKET and :RIGHTS' do + @data.cmsg_is?(:SOCKET, :RIGHTS).should == false + end + + it 'raises SocketError when comparing with :IPV6 and :RIGHTS' do + -> { @data.cmsg_is?(:IPV6, :RIGHTS) }.should raise_error(SocketError) + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/data_spec.rb b/spec/ruby/library/socket/ancillarydata/data_spec.rb new file mode 100644 index 0000000000..5a1a446dd5 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/data_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#data' do + it 'returns the data as a String' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, 'ugh').data.should == 'ugh' + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/family_spec.rb b/spec/ruby/library/socket/ancillarydata/family_spec.rb new file mode 100644 index 0000000000..975f0d2538 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/family_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#family' do + it 'returns the family as an Integer' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').family.should == Socket::AF_INET + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/initialize_spec.rb b/spec/ruby/library/socket/ancillarydata/initialize_spec.rb new file mode 100644 index 0000000000..eca45599d7 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/initialize_spec.rb @@ -0,0 +1,284 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#initialize' do + describe 'using Integers for the family, level, and type' do + before do + @data = Socket::AncillaryData + .new(Socket::AF_INET, Socket::IPPROTO_IP, Socket::IP_RECVTTL, 'ugh') + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using Symbols for the family, level, and type' do + before do + @data = Socket::AncillaryData.new(:INET, :IPPROTO_IP, :RECVTTL, 'ugh') + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using Strings for the family, level, and type' do + before do + @data = Socket::AncillaryData.new('INET', 'IPPROTO_IP', 'RECVTTL', 'ugh') + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using custom objects with a to_str method for the family, level, and type' do + before do + fmock = mock(:family) + lmock = mock(:level) + tmock = mock(:type) + dmock = mock(:data) + + fmock.stub!(:to_str).and_return('INET') + lmock.stub!(:to_str).and_return('IP') + tmock.stub!(:to_str).and_return('RECVTTL') + dmock.stub!(:to_str).and_return('ugh') + + @data = Socket::AncillaryData.new(fmock, lmock, tmock, dmock) + end + + it 'sets the address family' do + @data.family.should == Socket::AF_INET + end + + it 'sets the message level' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the message type' do + @data.type.should == Socket::IP_RECVTTL + end + + it 'sets the data' do + @data.data.should == 'ugh' + end + end + + describe 'using :AF_INET as the family and :SOCKET as the level' do + it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + + platform_is_not :aix do + it 'sets the type to SCM_TIMESTAMP when using :TIMESTAMP as the type argument' do + Socket::AncillaryData.new(:INET, :SOCKET, :TIMESTAMP, '').type.should == Socket::SCM_TIMESTAMP + end + end + + it 'raises TypeError when using a numeric string as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IGMP, Socket::SCM_RIGHTS.to_s, '') + }.should raise_error(TypeError) + end + + it 'raises SocketError when using :RECVTTL as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :SOCKET, :RECVTTL, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :SOCKET, :MOO, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :IP_RECVTTL as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :SOCKET, :IP_RECVTTL, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :SOCKET as the level' do + it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + end + + describe 'using :AF_INET as the family and :IP as the level' do + it 'sets the type to IP_RECVTTL when using :RECVTTL as the type argument' do + Socket::AncillaryData.new(:INET, :IP, :RECVTTL, '').type.should == Socket::IP_RECVTTL + end + + with_feature :ip_mtu do + it 'sets the type to IP_MTU when using :MTU as the type argument' do + Socket::AncillaryData.new(:INET, :IP, :MTU, '').type.should == Socket::IP_MTU + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IP, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IP, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :IPV6 as the level' do + it 'sets the type to IPV6_CHECKSUM when using :CHECKSUM as the type argument' do + Socket::AncillaryData.new(:INET, :IPV6, :CHECKSUM, '').type.should == Socket::IPV6_CHECKSUM + end + + with_feature :ipv6_nexthop do + it 'sets the type to IPV6_NEXTHOP when using :NEXTHOP as the type argument' do + Socket::AncillaryData.new(:INET, :IPV6, :NEXTHOP, '').type.should == Socket::IPV6_NEXTHOP + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IPV6, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :IPV6, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :TCP as the level' do + with_feature :tcp_cork do + it 'sets the type to TCP_CORK when using :CORK as the type argument' do + Socket::AncillaryData.new(:INET, :TCP, :CORK, '').type.should == Socket::TCP_CORK + end + end + + with_feature :tcp_info do + it 'sets the type to TCP_INFO when using :INFO as the type argument' do + Socket::AncillaryData.new(:INET, :TCP, :INFO, '').type.should == Socket::TCP_INFO + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :TCP, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :TCP, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_INET as the family and :UDP as the level' do + with_feature :udp_cork do + it 'sets the type to UDP_CORK when using :CORK as the type argument' do + Socket::AncillaryData.new(:INET, :UDP, :CORK, '').type.should == Socket::UDP_CORK + end + end + + it 'raises SocketError when using :RIGHTS as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :UDP, :RIGHTS, '') + }.should raise_error(SocketError) + end + + it 'raises SocketError when using :MOO as the type argument' do + -> { + Socket::AncillaryData.new(:INET, :UDP, :MOO, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :SOCKET as the level' do + it 'sets the type to SCM_RIGHTS when using :RIGHTS as the type argument' do + Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + + it 'raises SocketError when using :CORK sa the type argument' do + -> { + Socket::AncillaryData.new(:UNIX, :SOCKET, :CORK, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :IP as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :IP, :RECVTTL, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :IPV6 as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :IPV6, :NEXTHOP, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :TCP as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :TCP, :CORK, '') + }.should raise_error(SocketError) + end + end + + describe 'using :AF_UNIX as the family and :UDP as the level' do + it 'raises SocketError' do + -> { + Socket::AncillaryData.new(:UNIX, :UDP, :CORK, '') + }.should raise_error(SocketError) + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/int_spec.rb b/spec/ruby/library/socket/ancillarydata/int_spec.rb new file mode 100644 index 0000000000..fe41a30a1a --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/int_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData.int' do + before do + @data = Socket::AncillaryData.int(:INET, :SOCKET, :RIGHTS, 4) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET + end + + it 'sets the level SOL_SOCKET' do + @data.level.should == Socket::SOL_SOCKET + end + + it 'sets the type SCM_RIGHTS' do + @data.type.should == Socket::SCM_RIGHTS + end + + it 'sets the data to a packed String' do + @data.data.should == [4].pack('I') + end + end + + describe 'Socket::AncillaryData#int' do + it 'returns the data as an Integer' do + data = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, 4) + + data.int.should == 4 + end + + it 'raises when the data is not an Integer' do + data = Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, 'ugh') + + -> { data.int }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb b/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb new file mode 100644 index 0000000000..84910a038a --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb @@ -0,0 +1,145 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :pktinfo do + describe 'Socket::AncillaryData.ip_pktinfo' do + describe 'with a source address and index' do + before do + @data = Socket::AncillaryData.ip_pktinfo(Addrinfo.ip('127.0.0.1'), 4) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET + end + + it 'sets the level to IPPROTO_IP' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the type to IP_PKTINFO' do + @data.type.should == Socket::IP_PKTINFO + end + end + + describe 'with a source address, index, and destination address' do + before do + source = Addrinfo.ip('127.0.0.1') + dest = Addrinfo.ip('127.0.0.5') + @data = Socket::AncillaryData.ip_pktinfo(source, 4, dest) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET + end + + it 'sets the level to IPPROTO_IP' do + @data.level.should == Socket::IPPROTO_IP + end + + it 'sets the type to IP_PKTINFO' do + @data.type.should == Socket::IP_PKTINFO + end + end + end + + describe 'Socket::AncillaryData#ip_pktinfo' do + describe 'using an Addrinfo without a port number' do + before do + @source = Addrinfo.ip('127.0.0.1') + @dest = Addrinfo.ip('127.0.0.5') + @data = Socket::AncillaryData.ip_pktinfo(@source, 4, @dest) + end + + it 'returns an Array' do + @data.ip_pktinfo.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @info = @data.ip_pktinfo + end + + it 'stores an Addrinfo at index 0' do + @info[0].should be_an_instance_of(Addrinfo) + end + + it 'stores the ifindex at index 1' do + @info[1].should be_kind_of(Integer) + end + + it 'stores an Addrinfo at index 2' do + @info[2].should be_an_instance_of(Addrinfo) + end + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ip_pktinfo[0] + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.1' + end + + it 'is not the same object as the input Addrinfo' do + @addr.should_not equal @source + end + end + + describe 'the ifindex' do + it 'is an Integer' do + @data.ip_pktinfo[1].should == 4 + end + end + + describe 'the destination Addrinfo' do + before do + @addr = @data.ip_pktinfo[2] + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.5' + end + + it 'is not the same object as the input Addrinfo' do + @addr.should_not equal @dest + end + end + end + + describe 'using an Addrinfo with a port number' do + before do + @source = Addrinfo.tcp('127.0.0.1', 80) + @dest = Addrinfo.tcp('127.0.0.5', 85) + @data = Socket::AncillaryData.ip_pktinfo(@source, 4, @dest) + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ip_pktinfo[0] + end + + it 'does not contain a port number' do + @addr.ip_port.should == 0 + end + end + + describe 'the destination Addrinfo' do + before do + @addr = @data.ip_pktinfo[2] + end + + it 'does not contain a port number' do + @addr.ip_port.should == 0 + end + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb new file mode 100644 index 0000000000..f70fe27d6a --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb @@ -0,0 +1,11 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :ipv6_pktinfo do + describe 'Socket::AncillaryData#ipv6_pktinfo_addr' do + it 'returns an Addrinfo' do + data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4) + + data.ipv6_pktinfo_addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb new file mode 100644 index 0000000000..bda37eec98 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb @@ -0,0 +1,11 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :ipv6_pktinfo do + describe 'Socket::AncillaryData#ipv6_pktinfo_ifindex' do + it 'returns an Addrinfo' do + data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4) + + data.ipv6_pktinfo_ifindex.should == 4 + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb new file mode 100644 index 0000000000..0fffc720dc --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb @@ -0,0 +1,89 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data, :ipv6_pktinfo do + describe 'Socket::AncillaryData.ipv6_pktinfo' do + before do + @data = Socket::AncillaryData.ipv6_pktinfo(Addrinfo.ip('::1'), 4) + end + + it 'returns a Socket::AncillaryData' do + @data.should be_an_instance_of(Socket::AncillaryData) + end + + it 'sets the family to AF_INET' do + @data.family.should == Socket::AF_INET6 + end + + it 'sets the level to IPPROTO_IP' do + @data.level.should == Socket::IPPROTO_IPV6 + end + + it 'sets the type to IP_PKTINFO' do + @data.type.should == Socket::IPV6_PKTINFO + end + end + + describe 'Socket::AncillaryData#ipv6_pktinfo' do + describe 'using an Addrinfo without a port number' do + before do + @source = Addrinfo.ip('::1') + @data = Socket::AncillaryData.ipv6_pktinfo(@source, 4) + end + + it 'returns an Array' do + @data.ipv6_pktinfo.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @info = @data.ipv6_pktinfo + end + + it 'stores an Addrinfo at index 0' do + @info[0].should be_an_instance_of(Addrinfo) + end + + it 'stores the ifindex at index 1' do + @info[1].should be_kind_of(Integer) + end + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ipv6_pktinfo[0] + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '::1' + end + + it 'is not the same object as the input Addrinfo' do + @addr.should_not equal @source + end + end + + describe 'the ifindex' do + it 'is an Integer' do + @data.ipv6_pktinfo[1].should == 4 + end + end + end + + describe 'using an Addrinfo with a port number' do + before do + @source = Addrinfo.tcp('::1', 80) + @data = Socket::AncillaryData.ipv6_pktinfo(@source, 4) + end + + describe 'the source Addrinfo' do + before do + @addr = @data.ipv6_pktinfo[0] + end + + it 'does not contain a port number' do + @addr.ip_port.should == 0 + end + end + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/level_spec.rb b/spec/ruby/library/socket/ancillarydata/level_spec.rb new file mode 100644 index 0000000000..a2ff216f9d --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/level_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#level' do + it 'returns the level as an Integer' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').level.should == Socket::SOL_SOCKET + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/type_spec.rb b/spec/ruby/library/socket/ancillarydata/type_spec.rb new file mode 100644 index 0000000000..972beeeca0 --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/type_spec.rb @@ -0,0 +1,9 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData#type' do + it 'returns the type as an Integer' do + Socket::AncillaryData.new(:INET, :SOCKET, :RIGHTS, '').type.should == Socket::SCM_RIGHTS + end + end +end diff --git a/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb b/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb new file mode 100644 index 0000000000..95052fd91c --- /dev/null +++ b/spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb @@ -0,0 +1,61 @@ +require_relative '../spec_helper' + +with_feature :ancillary_data do + describe 'Socket::AncillaryData.unix_rights' do + describe 'using a list of IO objects' do + before do + @data = Socket::AncillaryData.unix_rights(STDOUT, STDERR) + end + + it 'sets the family to AF_UNIX' do + @data.family.should == Socket::AF_UNIX + end + + it 'sets the level to SOL_SOCKET' do + @data.level.should == Socket::SOL_SOCKET + end + + it 'sets the type to SCM_RIGHTS' do + @data.type.should == Socket::SCM_RIGHTS + end + + it 'sets the data to a String containing the file descriptors' do + @data.data.unpack('I*').should == [STDOUT.fileno, STDERR.fileno] + end + end + + describe 'using non IO objects' do + it 'raises TypeError' do + -> { Socket::AncillaryData.unix_rights(10) }.should raise_error(TypeError) + end + end + end + + describe 'Socket::AncillaryData#unix_rights' do + it 'returns the data as an Array of IO objects' do + data = Socket::AncillaryData.unix_rights(STDOUT, STDERR) + + data.unix_rights.should == [STDOUT, STDERR] + end + + it 'returns nil when the data is not a list of file descriptors' do + data = Socket::AncillaryData.new(:UNIX, :SOCKET, :RIGHTS, '') + + data.unix_rights.should be_nil + end + + it 'raises TypeError when the level is not SOL_SOCKET' do + data = Socket::AncillaryData.new(:INET, :IP, :RECVTTL, '') + + -> { data.unix_rights }.should raise_error(TypeError) + end + + platform_is_not :aix do + it 'raises TypeError when the type is not SCM_RIGHTS' do + data = Socket::AncillaryData.new(:INET, :SOCKET, :TIMESTAMP, '') + + -> { data.unix_rights }.should raise_error(TypeError) + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/close_read_spec.rb b/spec/ruby/library/socket/basicsocket/close_read_spec.rb new file mode 100644 index 0000000000..f317b34955 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/close_read_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#close_read" do + before :each do + @server = TCPServer.new(0) + end + + after :each do + @server.close unless @server.closed? + end + + it "closes the reading end of the socket" do + @server.close_read + -> { @server.read }.should raise_error(IOError) + end + + it 'does not raise when called on a socket already closed for reading' do + @server.close_read + @server.close_read + -> { @server.read }.should raise_error(IOError) + end + + it 'does not fully close the socket' do + @server.close_read + @server.closed?.should be_false + end + + it "fully closes the socket if it was already closed for writing" do + @server.close_write + @server.close_read + @server.closed?.should be_true + end + + it 'raises IOError when called on a fully closed socket' do + @server.close + -> { @server.close_read }.should raise_error(IOError) + end + + it "returns nil" do + @server.close_read.should be_nil + end +end diff --git a/spec/ruby/library/socket/basicsocket/close_write_spec.rb b/spec/ruby/library/socket/basicsocket/close_write_spec.rb new file mode 100644 index 0000000000..232cfbb7c6 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/close_write_spec.rb @@ -0,0 +1,48 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#close_write" do + before :each do + @server = TCPServer.new(0) + end + + after :each do + @server.close unless @server.closed? + end + + it "closes the writing end of the socket" do + @server.close_write + -> { @server.write("foo") }.should raise_error(IOError) + end + + it 'does not raise when called on a socket already closed for writing' do + @server.close_write + @server.close_write + -> { @server.write("foo") }.should raise_error(IOError) + end + + it 'does not fully close the socket' do + @server.close_write + @server.closed?.should be_false + end + + it "does not prevent reading" do + @server.close_write + @server.read(0).should == "" + end + + it "fully closes the socket if it was already closed for reading" do + @server.close_read + @server.close_write + @server.closed?.should be_true + end + + it 'raises IOError when called on a fully closed socket' do + @server.close + -> { @server.close_write }.should raise_error(IOError) + end + + it "returns nil" do + @server.close_write.should be_nil + end +end diff --git a/spec/ruby/library/socket/basicsocket/connect_address_spec.rb b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb new file mode 100644 index 0000000000..2e318fcb85 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/connect_address_spec.rb @@ -0,0 +1,152 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#connect_address' do + describe 'using an unbound socket' do + after do + @sock.close + end + + it 'raises SocketError' do + @sock = Socket.new(:INET, :STREAM) + + -> { @sock.connect_address }.should raise_error(SocketError) + end + end + + describe 'using a socket bound to 0.0.0.0' do + before do + @sock = Socket.new(:INET, :STREAM) + @sock.bind(Socket.sockaddr_in(0, '0.0.0.0')) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end + + it 'uses 127.0.0.1 as the IP address' do + @sock.connect_address.ip_address.should == '127.0.0.1' + end + + it 'uses the correct port number' do + @sock.connect_address.ip_port.should > 0 + end + + it 'uses AF_INET as the address family' do + @sock.connect_address.afamily.should == Socket::AF_INET + end + + it 'uses PF_INET as the address family' do + @sock.connect_address.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 + end + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using a socket bound to ::' do + before do + @sock = Socket.new(:INET6, :STREAM) + @sock.bind(Socket.sockaddr_in(0, '::')) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end + + it 'uses ::1 as the IP address' do + @sock.connect_address.ip_address.should == '::1' + end + + it 'uses the correct port number' do + @sock.connect_address.ip_port.should > 0 + end + + it 'uses AF_INET6 as the address family' do + @sock.connect_address.afamily.should == Socket::AF_INET6 + end + + it 'uses PF_INET6 as the address family' do + @sock.connect_address.pfamily.should == Socket::PF_INET6 + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 + end + end + end + + platform_is_not :aix do + describe 'using an unbound UNIX socket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + rm_r(@path) + end + + it 'raises SocketError' do + -> { @client.connect_address }.should raise_error(SocketError) + end + end + end + + describe 'using a bound UNIX socket' do + before do + @path = SocketSpecs.socket_path + @sock = UNIXServer.new(@path) + end + + after do + @sock.close + rm_r(@path) + end + + it 'returns an Addrinfo' do + @sock.connect_address.should be_an_instance_of(Addrinfo) + end + + it 'uses the correct socket path' do + @sock.connect_address.unix_path.should == @path + end + + it 'uses AF_UNIX as the address family' do + @sock.connect_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @sock.connect_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.connect_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @sock.connect_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb new file mode 100644 index 0000000000..a8800a8493 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb @@ -0,0 +1,103 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket.do_not_reverse_lookup" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + @socket = TCPSocket.new('127.0.0.1', @port) + end + + after :each do + @server.close unless @server.closed? + @socket.close unless @socket.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it "defaults to true" do + BasicSocket.do_not_reverse_lookup.should be_true + end + + it "causes 'peeraddr' to avoid name lookups" do + @socket.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + @socket.peeraddr.should == ["AF_INET", @port, "127.0.0.1", "127.0.0.1"] + end + + it "looks for hostnames when set to false" do + @socket.do_not_reverse_lookup = false + BasicSocket.do_not_reverse_lookup = false + @socket.peeraddr[2].should == SocketSpecs.hostname + end + + it "looks for numeric addresses when set to true" do + @socket.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + @socket.peeraddr[2].should == "127.0.0.1" + end +end + +describe :socket_do_not_reverse_lookup, shared: true do + it "inherits from BasicSocket.do_not_reverse_lookup when the socket is created" do + @socket = @method.call + reverse = BasicSocket.do_not_reverse_lookup + @socket.do_not_reverse_lookup.should == reverse + + BasicSocket.do_not_reverse_lookup = !reverse + @socket.do_not_reverse_lookup.should == reverse + end + + it "is true when BasicSocket.do_not_reverse_lookup is true" do + BasicSocket.do_not_reverse_lookup = true + @socket = @method.call + @socket.do_not_reverse_lookup.should == true + end + + it "is false when BasicSocket.do_not_reverse_lookup is false" do + BasicSocket.do_not_reverse_lookup = false + @socket = @method.call + @socket.do_not_reverse_lookup.should == false + end + + it "can be changed with #do_not_reverse_lookup=" do + @socket = @method.call + reverse = @socket.do_not_reverse_lookup + @socket.do_not_reverse_lookup = !reverse + @socket.do_not_reverse_lookup.should == !reverse + end +end + +describe "BasicSocket#do_not_reverse_lookup" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + @socket.close if @socket && !@socket.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + describe "for an TCPSocket.new socket" do + it_behaves_like :socket_do_not_reverse_lookup, -> { + TCPSocket.new('127.0.0.1', @port) + } + end + + describe "for an TCPServer#accept socket" do + before :each do + @client = TCPSocket.new('127.0.0.1', @port) + end + + after :each do + @client.close if @client && !@client.closed? + end + + it_behaves_like :socket_do_not_reverse_lookup, -> { + @server.accept + } + end +end diff --git a/spec/ruby/library/socket/basicsocket/for_fd_spec.rb b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb new file mode 100644 index 0000000000..9c9e6a8b55 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/for_fd_spec.rb @@ -0,0 +1,38 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket.for_fd" do + before :each do + @server = TCPServer.new(0) + @s2 = nil + end + + after :each do + @socket1.close if @socket1 + @server.close if @server + end + + it "return a Socket instance wrapped around the descriptor" do + @s2 = TCPServer.for_fd(@server.fileno) + @s2.autoclose = false + @s2.should be_kind_of(TCPServer) + @s2.fileno.should == @server.fileno + end + + it 'returns a new socket for a file descriptor' do + @socket1 = Socket.new(:INET, :DGRAM) + socket2 = Socket.for_fd(@socket1.fileno) + socket2.autoclose = false + + socket2.should be_an_instance_of(Socket) + socket2.fileno.should == @socket1.fileno + end + + it 'sets the socket into binary mode' do + @socket1 = Socket.new(:INET, :DGRAM) + socket2 = Socket.for_fd(@socket1.fileno) + socket2.autoclose = false + + socket2.binmode?.should be_true + end +end diff --git a/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb new file mode 100644 index 0000000000..2e03cd3684 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getpeereid_spec.rb @@ -0,0 +1,36 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#getpeereid' do + platform_is_not :windows do + describe 'using a UNIXSocket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + + rm_r(@path) + end + + it 'returns an Array with the user and group ID' do + @client.getpeereid.should == [Process.euid, Process.egid] + end + end + end + + describe 'using an IPSocket' do + after do + @sock.close + end + + it 'raises NoMethodError' do + @sock = TCPServer.new('127.0.0.1', 0) + -> { @sock.getpeereid }.should raise_error(NoMethodError) + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/getpeername_spec.rb b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb new file mode 100644 index 0000000000..0b93f02eef --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getpeername_spec.rb @@ -0,0 +1,25 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#getpeername" do + + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", @port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns the sockaddr of the other end of the connection" do + server_sockaddr = Socket.pack_sockaddr_in(@port, "127.0.0.1") + @client.getpeername.should == server_sockaddr + end + + it 'raises Errno::ENOTCONN for a disconnected socket' do + -> { @server.getpeername }.should raise_error(Errno::ENOTCONN) + end +end diff --git a/spec/ruby/library/socket/basicsocket/getsockname_spec.rb b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb new file mode 100644 index 0000000000..b33db088b6 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getsockname_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#getsockname" do + after :each do + @socket.closed?.should be_false + @socket.close + end + + it "returns the sockaddr associated with the socket" do + @socket = TCPServer.new("127.0.0.1", 0) + sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname) + sockaddr.should == [@socket.addr[1], "127.0.0.1"] + end + + it "works on sockets listening in ipaddr_any" do + @socket = TCPServer.new(0) + sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname) + ["::", "0.0.0.0", "::ffff:0.0.0.0"].include?(sockaddr[1]).should be_true + sockaddr[0].should == @socket.addr[1] + end + + it 'returns a default socket address for a disconnected socket' do + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + sockaddr = Socket.unpack_sockaddr_in(@socket.getsockname) + sockaddr.should == [0, "0.0.0.0"] + end +end diff --git a/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb new file mode 100644 index 0000000000..ce65d6c92b --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/getsockopt_spec.rb @@ -0,0 +1,188 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#getsockopt" do + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + end + + after :each do + @sock.closed?.should be_false + @sock.close + end + + platform_is_not :aix do + # A known bug in AIX. getsockopt(2) does not properly set + # the fifth argument for SO_TYPE, SO_OOBINLINE, SO_BROADCAST, etc. + + it "gets a socket option Socket::SO_TYPE" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE).to_s + n.should == [Socket::SOCK_STREAM].pack("i") + end + + it "gets a socket option Socket::SO_OOBINLINE" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + end + end + + it "gets a socket option Socket::SO_LINGER" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + if (n.size == 8) # linger struct on some platforms, not just a value + n.should == [0, 0].pack("ii") + else + n.should == [0].pack("i") + end + end + + it "gets a socket option Socket::SO_SNDBUF" do + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should > 0 + end + + it "raises a SystemCallError with an invalid socket option" do + -> { @sock.getsockopt Socket::SOL_SOCKET, -1 }.should raise_error(Errno::ENOPROTOOPT) + end + + it 'returns a Socket::Option using a constant' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE) + + opt.should be_an_instance_of(Socket::Option) + end + + it 'returns a Socket::Option for a boolean option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR) + + opt.bool.should == false + end + + it 'returns a Socket::Option for a numeric option' do + opt = @sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL) + + opt.int.should be_kind_of(Integer) + end + + it 'returns a Socket::Option for a struct option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER) + + opt.linger.should == [false, 0] + end + + it 'raises Errno::ENOPROTOOPT when requesting an invalid option' do + -> { @sock.getsockopt(Socket::SOL_SOCKET, -1) }.should raise_error(Errno::ENOPROTOOPT) + end + + describe 'using Symbols as arguments' do + it 'returns a Socket::Option for arguments :SOCKET and :TYPE' do + opt = @sock.getsockopt(:SOCKET, :TYPE) + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_TYPE + end + + it 'returns a Socket::Option for arguments :IP and :TTL' do + opt = @sock.getsockopt(:IP, :TTL) + + opt.level.should == Socket::IPPROTO_IP + opt.optname.should == Socket::IP_TTL + end + + it 'returns a Socket::Option for arguments :SOCKET and :REUSEADDR' do + opt = @sock.getsockopt(:SOCKET, :REUSEADDR) + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_REUSEADDR + end + + it 'returns a Socket::Option for arguments :SOCKET and :LINGER' do + opt = @sock.getsockopt(:SOCKET, :LINGER) + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_LINGER + end + + with_feature :udp_cork do + it 'returns a Socket::Option for arguments :UDP and :CORK' do + sock = Socket.new(:INET, :DGRAM) + begin + opt = sock.getsockopt(:UDP, :CORK) + + opt.level.should == Socket::IPPROTO_UDP + opt.optname.should == Socket::UDP_CORK + ensure + sock.close + end + end + end + end + + describe 'using Strings as arguments' do + it 'returns a Socket::Option for arguments "SOCKET" and "TYPE"' do + opt = @sock.getsockopt("SOCKET", "TYPE") + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_TYPE + end + + it 'returns a Socket::Option for arguments "IP" and "TTL"' do + opt = @sock.getsockopt("IP", "TTL") + + opt.level.should == Socket::IPPROTO_IP + opt.optname.should == Socket::IP_TTL + end + + it 'returns a Socket::Option for arguments "SOCKET" and "REUSEADDR"' do + opt = @sock.getsockopt("SOCKET", "REUSEADDR") + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_REUSEADDR + end + + it 'returns a Socket::Option for arguments "SOCKET" and "LINGER"' do + opt = @sock.getsockopt("SOCKET", "LINGER") + + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_LINGER + end + + with_feature :udp_cork do + it 'returns a Socket::Option for arguments "UDP" and "CORK"' do + sock = Socket.new("INET", "DGRAM") + begin + opt = sock.getsockopt("UDP", "CORK") + + opt.level.should == Socket::IPPROTO_UDP + opt.optname.should == Socket::UDP_CORK + ensure + sock.close + end + end + end + end + + describe 'using a String based option' do + it 'allows unpacking of a boolean option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR).to_s + + opt.unpack('i').should == [0] + end + + it 'allows unpacking of a numeric option' do + opt = @sock.getsockopt(Socket::IPPROTO_IP, Socket::IP_TTL).to_s + array = opt.unpack('i') + + array[0].should be_kind_of(Integer) + array[0].should > 0 + end + + it 'allows unpacking of a struct option' do + opt = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + + if opt.bytesize == 8 + opt.unpack('ii').should == [0, 0] + else + opt.unpack('i').should == [0] + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/ioctl_spec.rb b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb new file mode 100644 index 0000000000..615d92bea8 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/ioctl_spec.rb @@ -0,0 +1,42 @@ +require_relative '../spec_helper' + +describe "Socket::BasicSocket#ioctl" do + platform_is :linux do + it "passes data from and to a String correctly" do + s = Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, 0 + # /usr/include/net/if.h, structure ifreq + # The structure is 32 bytes on x86, 40 bytes on x86_64 + if_name = ['lo'].pack('a16') + buffer = if_name + 'z' * 24 + # SIOCGIFADDR in /usr/include/bits/ioctls.h + s.ioctl 0x8915, buffer + s.close + + # Interface name should remain unchanged. + buffer[0, 16].should == if_name + # lo should have an IPv4 address of 127.0.0.1 + buffer[16, 2].unpack('S!').first.should == Socket::AF_INET + buffer[20, 4].should == "\x7f\0\0\x01" + end + end + + platform_is :freebsd do + it "passes data from and to a String correctly" do + s = Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, 0 + # /usr/include/net/if.h, structure ifreq + # The structure is 32 bytes on x86, 40 bytes on x86_64 + if_name = ['lo0'].pack('a16') + buffer = if_name + 'z' * 24 + # SIOCGIFADDR in /usr/include/bits/ioctls.h + s.ioctl 0xc0206921, buffer + s.close + + # Interface name should remain unchanged. + buffer[0, 16].should == if_name + # lo should have an IPv4 address of 127.0.0.1 + buffer[16, 1].unpack('C').first.should == 16 + buffer[17, 1].unpack('C').first.should == Socket::AF_INET + buffer[20, 4].should == "\x7f\0\0\x01" + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/local_address_spec.rb b/spec/ruby/library/socket/basicsocket/local_address_spec.rb new file mode 100644 index 0000000000..0bd60a44cd --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/local_address_spec.rb @@ -0,0 +1,10 @@ +require_relative '../spec_helper' +require_relative '../shared/address' + +describe 'BasicSocket#local_address' do + it_behaves_like :socket_local_remote_address, :local_address, -> socket { + a2 = BasicSocket.for_fd(socket.fileno) + a2.autoclose = false + a2.local_address + } +end diff --git a/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb new file mode 100644 index 0000000000..ea5e65da5c --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb @@ -0,0 +1,74 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#read_nonblock" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.send("aaa", 0, @r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "receives data after it's ready" do + IO.select([@r], nil, nil, 2) + @r.read_nonblock(5).should == "aaa" + end + + platform_is_not :windows do + it 'returned data is binary encoded regardless of the external encoding' do + IO.select([@r], nil, nil, 2) + @r.read_nonblock(1).encoding.should == Encoding::BINARY + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + IO.select([@r], nil, nil, 2) + buffer = @r.read_nonblock(3) + buffer.should == "bbb" + buffer.encoding.should == Encoding::BINARY + end + end + + it 'replaces the content of the provided buffer without changing its encoding' do + buffer = "initial data".dup.force_encoding(Encoding::UTF_8) + + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3, buffer) + buffer.should == "aaa" + buffer.encoding.should == Encoding::UTF_8 + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3, buffer) + buffer.should == "bbb" + buffer.encoding.should == Encoding::UTF_8 + end + + platform_is :linux do + it 'does not set the IO in nonblock mode' do + require 'io/nonblock' + @r.nonblock = false + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3).should == "aaa" + @r.should_not.nonblock? + end + end + + platform_is_not :linux, :windows do + it 'sets the IO in nonblock mode' do + require 'io/nonblock' + @r.nonblock = false + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3).should == "aaa" + @r.should.nonblock? + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/read_spec.rb b/spec/ruby/library/socket/basicsocket/read_spec.rb new file mode 100644 index 0000000000..ba9de7d5cf --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/read_spec.rb @@ -0,0 +1,47 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#read" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.send("aaa", 0, @r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "receives data after it's ready" do + @r.read(3).should == "aaa" + end + + it 'returned data is binary encoded regardless of the external encoding' do + @r.read(3).encoding.should == Encoding::BINARY + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::UTF_8) + buffer = @r.read(3) + buffer.should == "bbb" + buffer.encoding.should == Encoding::BINARY + end + + it 'replaces the content of the provided buffer without changing its encoding' do + buffer = "initial data".dup.force_encoding(Encoding::UTF_8) + + @r.read(3, buffer) + buffer.should == "aaa" + buffer.encoding.should == Encoding::UTF_8 + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + @r.read(3, buffer) + buffer.should == "bbb" + buffer.encoding.should == Encoding::UTF_8 + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb new file mode 100644 index 0000000000..f2a6682f12 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb @@ -0,0 +1,172 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::BasicSocket#recv_nonblock" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @s1 = Socket.new(family, :DGRAM) + @s2 = Socket.new(family, :DGRAM) + end + + after :each do + @s1.close unless @s1.closed? + @s2.close unless @s2.closed? + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises an exception extending IO::WaitReadable' do + -> { @s1.recv_nonblock(1) }.should raise_error(IO::WaitReadable) + end + end + end + + it "raises an exception extending IO::WaitReadable if there's no data available" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + -> { + @s1.recv_nonblock(5) + }.should raise_error(IO::WaitReadable) { |e| + platform_is_not :windows do + e.should be_kind_of(Errno::EAGAIN) + end + platform_is :windows do + e.should be_kind_of(Errno::EWOULDBLOCK) + end + } + end + + it "returns :wait_readable with exception: false" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s1.recv_nonblock(5, exception: false).should == :wait_readable + end + + it "receives data after it's ready" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("aaa", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + @s1.recv_nonblock(5).should == "aaa" + end + + it "allows an output buffer as third argument" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("data", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + + buffer = +"foo" + @s1.recv_nonblock(5, 0, buffer).should.equal?(buffer) + buffer.should == "data" + end + + it "preserves the encoding of the given buffer" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("data", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + + buffer = ''.encode(Encoding::ISO_8859_1) + @s1.recv_nonblock(5, 0, buffer) + buffer.encoding.should == Encoding::ISO_8859_1 + end + + it "does not block if there's no data available" do + @s1.bind(Socket.pack_sockaddr_in(0, ip_address)) + @s2.send("a", 0, @s1.getsockname) + IO.select([@s1], nil, nil, 2) + @s1.recv_nonblock(1).should == "a" + -> { + @s1.recv_nonblock(5) + }.should raise_error(IO::WaitReadable) + end + end + + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a connected but not bound socket' do + before do + @server = Socket.new(family, :STREAM) + end + + after do + @server.close + end + + it "raises Errno::ENOTCONN" do + -> { @server.recv_nonblock(1) }.should raise_error { |e| + [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class) + } + -> { @server.recv_nonblock(1, exception: false) }.should raise_error { |e| + [Errno::ENOTCONN, Errno::EINVAL].should.include?(e.class) + } + end + end + end +end + +describe "Socket::BasicSocket#recv_nonblock" do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recv_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recv_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should be_nil + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb new file mode 100644 index 0000000000..a51920f52a --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb @@ -0,0 +1,250 @@ +# encoding: binary +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#recv" do + + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + ScratchPad.clear + end + + it "receives a specified number of bytes of a message from another socket" do + t = Thread.new do + client = @server.accept + ScratchPad.record client.recv(10) + client.recv(1) # this recv is important + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.send('hello', 0) + socket.close + + t.join + ScratchPad.recorded.should == 'hello' + end + + it "accepts flags to specify unusual receiving behaviour" do + t = Thread.new do + client = @server.accept + + # in-band data (TCP), doesn't receive the flag. + ScratchPad.record client.recv(10) + + # this recv is important (TODO: explain) + client.recv(10) + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.send('helloU', Socket::MSG_OOB) + socket.shutdown(1) + t.join + socket.close + ScratchPad.recorded.should == 'hello' + end + + it "gets lines delimited with a custom separator" do + t = Thread.new do + client = @server.accept + ScratchPad.record client.gets("\377") + + # this call is important (TODO: explain) + client.gets(nil) + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("firstline\377secondline\377") + socket.close + + t.join + ScratchPad.recorded.should == "firstline\377" + end + + it "allows an output buffer as third argument" do + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("data") + + client = @server.accept + buffer = +"foo" + begin + client.recv(4, 0, buffer).should.equal?(buffer) + ensure + client.close + end + buffer.should == "data" + + socket.close + end + + it "preserves the encoding of the given buffer" do + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("data") + + client = @server.accept + buffer = ''.encode(Encoding::ISO_8859_1) + begin + client.recv(4, 0, buffer) + ensure + client.close + end + buffer.encoding.should == Encoding::ISO_8859_1 + + socket.close + end +end + +describe 'BasicSocket#recv' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + describe 'using an unbound socket' do + it 'blocks the caller' do + -> { @server.recv(4) }.should block_caller + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + describe 'without any data available' do + it 'blocks the caller' do + -> { @server.recv(4) }.should block_caller + end + end + + describe 'with data available' do + before do + @client.connect(@server.getsockname) + end + + it 'reads the given amount of bytes' do + @client.write('hello') + + @server.recv(2).should == 'he' + end + + it 'reads the given amount of bytes when it exceeds the data size' do + @client.write('he') + + @server.recv(6).should == 'he' + end + + it 'blocks the caller when called twice without new data being available' do + @client.write('hello') + + @server.recv(2).should == 'he' + + -> { @server.recv(4) }.should block_caller + end + + it 'takes a peek at the data when using the MSG_PEEK flag' do + @client.write('hello') + + @server.recv(2, Socket::MSG_PEEK).should == 'he' + @server.recv(2).should == 'he' + end + end + end + end +end + +describe "BasicSocket#recv" do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recv(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recv(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should be_nil + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns empty String" do + @server.bind(ip_address, 0) + addr = @server.connect_address + @client.connect(addr.ip_address, addr.ip_port) + + @client.send('', 0) + + @server.recv(1).should == "" + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb new file mode 100644 index 0000000000..b5fdd7c93b --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb @@ -0,0 +1,300 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#recvmsg_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises an exception extending IO::WaitReadable' do + -> { @server.recvmsg_nonblock }.should raise_error(IO::WaitReadable) + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + describe 'without any data available' do + it 'raises an exception extending IO::WaitReadable' do + -> { @server.recvmsg_nonblock }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable with exception: false' do + @server.recvmsg_nonblock(exception: false).should == :wait_readable + end + end + + describe 'with data available' do + before do + @client.connect(@server.getsockname) + + @client.write('hello') + + IO.select([@server], nil, nil, 5) + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @server.recvmsg_nonblock.should be_an_instance_of(Array) + end + + describe 'without a maximum message length' do + it 'reads all the available data' do + @server.recvmsg_nonblock[0].should == 'hello' + end + end + + describe 'with a maximum message length' do + platform_is_not :windows do + it 'reads up to the maximum amount of bytes' do + @server.recvmsg_nonblock(2)[0].should == 'he' + end + end + end + + describe 'the returned Array' do + before do + @array = @server.recvmsg_nonblock + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + platform_is_not :windows do + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the correct address family' do + @addr.afamily.should == family + end + + it 'uses the correct protocol family' do + @addr.pfamily.should == family + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the port number of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end + + platform_is_not :windows do + describe 'using a connected but not bound socket' do + before do + @server = Socket.new(family, :STREAM) + end + + after do + @server.close + end + + it "raises Errno::ENOTCONN" do + -> { @server.recvmsg_nonblock }.should raise_error(Errno::ENOTCONN) + -> { @server.recvmsg_nonblock(exception: false) }.should raise_error(Errno::ENOTCONN) + end + end + + describe 'using a connected socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'without any data available' do + it 'raises IO::WaitReadable' do + -> { + socket, _ = @server.accept + begin + socket.recvmsg_nonblock + ensure + socket.close + end + }.should raise_error(IO::WaitReadable) + end + end + + describe 'with data available' do + before do + @client.write('hello') + + @socket, _ = @server.accept + IO.select([@socket]) + end + + after do + @socket.close + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @socket.recvmsg_nonblock.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @socket.recvmsg_nonblock + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'raises when receiving the ip_address message' do + -> { @addr.ip_address }.should raise_error(SocketError) + end + + it 'uses the correct address family' do + @addr.afamily.should == Socket::AF_UNSPEC + end + + it 'uses 0 for the protocol family' do + @addr.pfamily.should == 0 + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'raises when receiving the ip_port message' do + -> { @addr.ip_port }.should raise_error(SocketError) + end + end + end + end + end + end + end +end + +describe 'BasicSocket#recvmsg_nonblock' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + platform_is_not :windows do # #recvmsg_nonblock() raises 'Errno::EINVAL: Invalid argument - recvmsg(2)' + it "returns an empty String as received data on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recvmsg_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + end + + ruby_version_is "3.3" do + platform_is_not :windows do + it "returns nil on a closed stream socket" do + ready = false + + t = Thread.new do + client = @server.accept + + Thread.pass while !ready + begin + client.recvmsg_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true + + t.value.should be_nil + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb new file mode 100644 index 0000000000..04ba1d74c7 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb @@ -0,0 +1,281 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#recvmsg' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'blocks the caller' do + -> { @server.recvmsg }.should block_caller + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + describe 'without any data available' do + it 'blocks the caller' do + -> { @server.recvmsg }.should block_caller + end + end + + describe 'with data available' do + before do + @client.connect(@server.getsockname) + + @client.write('hello') + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @server.recvmsg.should be_an_instance_of(Array) + end + + describe 'without a maximum message length' do + it 'reads all the available data' do + @server.recvmsg[0].should == 'hello' + end + end + + describe 'with a maximum message length' do + it 'reads up to the maximum amount of bytes' do + @server.recvmsg(2)[0].should == 'he' + end + end + + describe 'the returned Array' do + before do + @array = @server.recvmsg + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + platform_is_not :windows do + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the correct address family' do + @addr.afamily.should == family + end + + it 'uses the correct protocol family' do + @addr.pfamily.should == family + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the port number of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end + + platform_is_not :windows do + describe 'using a connected socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'without any data available' do + it 'blocks the caller' do + socket, _ = @server.accept + begin + -> { socket.recvmsg }.should block_caller + ensure + socket.close + end + end + end + + describe 'with data available' do + before do + @client.write('hello') + @socket, _ = @server.accept + end + + after do + @socket.close + end + + it 'returns an Array containing the data, an Addrinfo and the flags' do + @socket.recvmsg.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = @socket.recvmsg + end + + it 'stores the message at index 0' do + @array[0].should == 'hello' + end + + it 'stores an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + + it 'stores the flags at index 2' do + @array[2].should be_kind_of(Integer) + end + + describe 'the returned Addrinfo' do + before do + @addr = @array[1] + end + + it 'raises when receiving the ip_address message' do + -> { @addr.ip_address }.should raise_error(SocketError) + end + + it 'uses the correct address family' do + @addr.afamily.should == Socket::AF_UNSPEC + end + + it 'returns 0 for the protocol family' do + @addr.pfamily.should == 0 + end + + it 'uses the correct socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'raises when receiving the ip_port message' do + -> { @addr.ip_port }.should raise_error(SocketError) + end + end + end + end + end + end + end +end + +describe 'BasicSocket#recvmsg' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + ruby_version_is ""..."3.3" do + platform_is_not :windows do + it "returns an empty String as received data on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recvmsg(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + end + + ruby_version_is "3.3" do + platform_is_not :windows do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recvmsg(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + + t.value.should be_nil + end + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns an empty String as received data" do + @server.bind(ip_address, 0) + addr = @server.connect_address + @client.connect(addr.ip_address, addr.ip_port) + + @client.send('', 0) + message = @server.recvmsg(1) + + message.should.is_a? Array + message[0].should == "" + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/remote_address_spec.rb b/spec/ruby/library/socket/basicsocket/remote_address_spec.rb new file mode 100644 index 0000000000..439bf31592 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/remote_address_spec.rb @@ -0,0 +1,10 @@ +require_relative '../spec_helper' +require_relative '../shared/address' + +describe 'BasicSocket#remote_address' do + it_behaves_like :socket_local_remote_address, :remote_address, -> socket { + a2 = BasicSocket.for_fd(socket.fileno) + a2.autoclose = false + a2.remote_address + } +end diff --git a/spec/ruby/library/socket/basicsocket/send_spec.rb b/spec/ruby/library/socket/basicsocket/send_spec.rb new file mode 100644 index 0000000000..25ba3f5655 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/send_spec.rb @@ -0,0 +1,220 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#send" do + before :each do + @server = TCPServer.new('127.0.0.1', 0) + @port = @server.addr[1] + @socket = TCPSocket.new('127.0.0.1', @port) + end + + after :each do + @server.closed?.should be_false + @socket.closed?.should be_false + + @server.close + @socket.close + end + + it "sends a message to another socket and returns the number of bytes sent" do + data = +"" + t = Thread.new do + client = @server.accept + loop do + got = client.recv(5) + break if got.nil? || got.empty? + data << got + end + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @socket.send('hello', 0).should == 5 + @socket.shutdown(1) # indicate, that we are done sending + @socket.recv(10) + + t.join + data.should == 'hello' + end + + platform_is_not :windows do + it "accepts flags to specify unusual sending behaviour" do + data = nil + peek_data = nil + t = Thread.new do + client = @server.accept + peek_data = client.recv(6, Socket::MSG_PEEK) + data = client.recv(6) + client.recv(10) # this recv is important + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @socket.send('helloU', Socket::MSG_PEEK | Socket::MSG_OOB).should == 6 + @socket.shutdown # indicate, that we are done sending + + t.join + peek_data.should == "hello" + data.should == 'hello' + end + end + + it "accepts a sockaddr as recipient address" do + data = +"" + t = Thread.new do + client = @server.accept + loop do + got = client.recv(5) + break if got.nil? || got.empty? + data << got + end + client.close + end + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + sockaddr = Socket.pack_sockaddr_in(@port, "127.0.0.1") + @socket.send('hello', 0, sockaddr).should == 5 + @socket.shutdown # indicate, that we are done sending + + t.join + data.should == 'hello' + end +end + +describe 'BasicSocket#send' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'with an object implementing #to_str' do + it 'returns the amount of sent bytes' do + data = mock('message') + data.should_receive(:to_str).and_return('hello') + @client.send(data, 0, @server.getsockname).should == 5 + end + end + + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as a String' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @server.getsockname).should == 5 + end + + it 'does not persist the connection after writing to the socket' do + @client.send('hello', 0, @server.getsockname) + + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as an Addrinfo' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @server.connect_address).should == 5 + end + end + end + + describe 'using a connected UDP socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address argument' do + before do + @client.connect(@server.getsockname) + end + + it 'returns the amount of bytes written' do + @client.send('hello', 0).should == 5 + end + end + + describe 'with a destination address argument' do + before do + @alt_server = Socket.new(family, :DGRAM) + + @alt_server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @alt_server.close + end + + it 'sends the message to the given address instead' do + @client.send('hello', 0, @alt_server.getsockname).should == 5 + + -> { @server.recv(5) }.should block_caller + + @alt_server.recv(5).should == 'hello' + end + + it 'does not persist the alternative connection after writing to the socket' do + @client.send('hello', 0, @alt_server.getsockname) + + @client.connect(@server.getsockname) + @client.send('world', 0) + + @server.recv(5).should == 'world' + end + end + end + + platform_is_not :darwin, :windows do + describe 'using a connected TCP socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'using the MSG_OOB flag' do + it 'sends an out-of-band message' do + socket, _ = @server.accept + socket.setsockopt(:SOCKET, :OOBINLINE, true) + @client.send('a', Socket::MSG_OOB).should == 1 + begin + socket.recv(10).should == 'a' + ensure + socket.close + end + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb new file mode 100644 index 0000000000..7acfc659bd --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb @@ -0,0 +1,118 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#sendmsg_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { + @client.sendmsg_nonblock('hello') + }.should raise_error(SocketSpecs.dest_addr_req_error) + -> { + @client.sendmsg_nonblock('hello', exception: false) + }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as a String' do + it 'returns the amount of sent bytes' do + @client.sendmsg_nonblock('hello', 0, @server.getsockname).should == 5 + end + end + + describe 'with a destination address as an Addrinfo' do + it 'returns the amount of sent bytes' do + @client.sendmsg_nonblock('hello', 0, @server.connect_address).should == 5 + end + end + end + + describe 'using a connected UDP socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address argument' do + before do + @client.connect(@server.getsockname) + end + + it 'returns the amount of bytes written' do + @client.sendmsg_nonblock('hello').should == 5 + end + end + + describe 'with a destination address argument' do + before do + @alt_server = Socket.new(family, :DGRAM) + @alt_server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @alt_server.close + end + + it 'sends the message to the given address instead' do + @client.sendmsg_nonblock('hello', 0, @alt_server.getsockname).should == 5 + -> { @server.recv(5) }.should block_caller + @alt_server.recv(5).should == 'hello' + end + end + end + + platform_is_not :windows do + describe 'using a connected TCP socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + it 'raises IO::WaitWritable when the underlying buffer is full' do + -> { + 10.times { @client.sendmsg_nonblock('hello' * 1_000_000) } + }.should raise_error(IO::WaitWritable) + end + + it 'returns :wait_writable when the underlying buffer is full with exception: false' do + ret = nil + 10.times { + ret = @client.sendmsg_nonblock('hello' * 1_000_000, exception: false) + break unless ret.is_a?(Integer) + } + ret.should == :wait_writable + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb b/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb new file mode 100644 index 0000000000..7ff336c0b7 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/sendmsg_spec.rb @@ -0,0 +1,111 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'BasicSocket#sendmsg' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a disconnected socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { @client.sendmsg('hello') }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + end + + describe 'with a destination address as a String' do + it 'returns the amount of sent bytes' do + @client.sendmsg('hello', 0, @server.getsockname).should == 5 + end + end + + describe 'with a destination address as an Addrinfo' do + it 'returns the amount of sent bytes' do + @client.sendmsg('hello', 0, @server.connect_address).should == 5 + end + end + end + + describe 'using a connected UDP socket' do + before do + @client = Socket.new(family, :DGRAM) + @server = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + describe 'without a destination address argument' do + before do + @client.connect(@server.getsockname) + end + + it 'returns the amount of bytes written' do + @client.sendmsg('hello').should == 5 + end + end + + describe 'with a destination address argument' do + before do + @alt_server = Socket.new(family, :DGRAM) + + @alt_server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @alt_server.close + end + + it 'sends the message to the given address instead' do + @client.sendmsg('hello', 0, @alt_server.getsockname).should == 5 + + -> { @server.recv(5) }.should block_caller + + @alt_server.recv(5).should == 'hello' + end + end + end + + platform_is_not :windows do # spurious + describe 'using a connected TCP socket' do + before do + @client = Socket.new(family, :STREAM) + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + it 'blocks when the underlying buffer is full' do + # Buffer sizes may differ per platform, so sadly this is the only + # reliable way of testing blocking behaviour. + -> do + 10.times { @client.sendmsg('hello' * 1_000_000) } + end.should block_caller + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb new file mode 100644 index 0000000000..f686e67326 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/setsockopt_spec.rb @@ -0,0 +1,334 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#setsockopt" do + + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + end + + after :each do + @sock.close unless @sock.closed? + end + + it "sets the socket linger to 0" do + linger = [0, 0].pack("ii") + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + + if (n.size == 8) # linger struct on some platforms, not just a value + n.should == [0, 0].pack("ii") + else + n.should == [0].pack("i") + end + end + + it "sets the socket linger to some positive value" do + linger = [64, 64].pack("ii") + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).to_s + if (n.size == 8) # linger struct on some platforms, not just a value + a = n.unpack('ii') + a[0].should_not == 0 + a[1].should == 64 + else + n.should == [64].pack("i") + end + end + + platform_is_not :windows do + it "raises EINVAL if passed wrong linger value" do + -> do + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, 0) + end.should raise_error(Errno::EINVAL) + end + end + + platform_is_not :aix do + # A known bug in AIX. getsockopt(2) does not properly set + # the fifth argument for SO_TYPE, SO_OOBINLINE, SO_BROADCAST, etc. + + it "sets the socket option Socket::SO_OOBINLINE" do + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, true).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, false).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 0).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 2).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "") + }.should raise_error(SystemCallError) + end + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "blah").should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "0") + }.should raise_error(SystemCallError) + end + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00\x00").should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "1") + }.should raise_error(SystemCallError) + end + + platform_is_not :windows do + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, "\x00\x00\x00") + }.should raise_error(SystemCallError) + end + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [0].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should == [0].pack("i") + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, [1000].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE).to_s + n.should_not == [0].pack("i") + end + end + + it "sets the socket option Socket::SO_SNDBUF" do + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 4000).should == 0 + sndbuf = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + # might not always be possible to set to exact size + sndbuf.unpack('i')[0].should >= 4000 + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, true).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 1 + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, nil).should == 0 + }.should raise_error(TypeError) + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 1).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 1 + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 2).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 2 + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "bla") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "0") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "1") + }.should raise_error(SystemCallError) + + -> { + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x00") + }.should raise_error(SystemCallError) + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, "\x00\x00\x01\x00").should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= "\x00\x00\x01\x00".unpack('i')[0] + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, [4000].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 4000 + + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, [1000].pack('i')).should == 0 + n = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF).to_s + n.unpack('i')[0].should >= 1000 + end + + platform_is_not :aix do + describe 'accepts Socket::Option as argument' do + it 'boolean' do + option = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + @sock.setsockopt(option).should == 0 + @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool.should == true + end + + it 'int' do + option = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + @sock.setsockopt(option).should == 0 + @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool.should == true + end + end + end + + platform_is :aix do + describe 'accepts Socket::Option as argument' do + it 'boolean' do + option = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + @sock.setsockopt(option).should == 0 + end + + it 'int' do + option = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + @sock.setsockopt(option).should == 0 + end + end + end + + describe 'accepts Socket::Option as argument' do + it 'linger' do + option = Socket::Option.linger(true, 10) + @sock.setsockopt(option).should == 0 + onoff, seconds = @sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER).linger + seconds.should == 10 + # Both results can be produced depending on the OS and value of Socket::SO_LINGER + [true, Socket::SO_LINGER].should include(onoff) + end + end +end + +describe 'BasicSocket#setsockopt' do + describe 'using a STREAM socket' do + before do + @socket = Socket.new(:INET, :STREAM) + end + + after do + @socket.close + end + + describe 'using separate arguments with Symbols' do + it 'raises TypeError when the first argument is nil' do + -> { @socket.setsockopt(nil, :REUSEADDR, true) }.should raise_error(TypeError) + end + + it 'sets a boolean option' do + @socket.setsockopt(:SOCKET, :REUSEADDR, true).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt(:IP, :TTL, 255).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + + guard -> { SocketSpecs.ipv6_available? } do + it 'sets an IPv6 boolean option' do + socket = Socket.new(:INET6, :STREAM) + begin + socket.setsockopt(:IPV6, :V6ONLY, true).should == 0 + socket.getsockopt(:IPV6, :V6ONLY).bool.should == true + ensure + socket.close + end + end + end + + platform_is_not :windows do + it 'raises Errno::EINVAL when setting an invalid option value' do + -> { @socket.setsockopt(:SOCKET, :OOBINLINE, 'bla') }.should raise_error(Errno::EINVAL) + end + end + end + + describe 'using separate arguments with Symbols' do + it 'sets a boolean option' do + @socket.setsockopt('SOCKET', 'REUSEADDR', true).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt('IP', 'TTL', 255).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + end + + describe 'using separate arguments with constants' do + it 'sets a boolean option' do + @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, 255).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + end + + describe 'using separate arguments with custom objects' do + it 'sets a boolean option' do + level = mock(:level) + name = mock(:name) + + level.stub!(:to_str).and_return('SOCKET') + name.stub!(:to_str).and_return('REUSEADDR') + + @socket.setsockopt(level, name, true).should == 0 + end + end + + describe 'using a Socket::Option as the first argument' do + it 'sets a boolean option' do + @socket.setsockopt(Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true)).should == 0 + @socket.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + + it 'sets an integer option' do + @socket.setsockopt(Socket::Option.int(:INET, :IP, :TTL, 255)).should == 0 + @socket.getsockopt(:IP, :TTL).int.should == 255 + end + + it 'raises ArgumentError when passing 2 arguments' do + option = Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true) + -> { @socket.setsockopt(option, :REUSEADDR) }.should raise_error(ArgumentError) + end + + it 'raises TypeError when passing 3 arguments' do + option = Socket::Option.bool(:INET, :SOCKET, :REUSEADDR, true) + -> { @socket.setsockopt(option, :REUSEADDR, true) }.should raise_error(TypeError) + end + end + end + + describe 'using a UNIX socket' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r @path + end + + it 'sets a boolean option' do + @server.setsockopt(:SOCKET, :REUSEADDR, true) + @server.getsockopt(:SOCKET, :REUSEADDR).bool.should == true + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/shutdown_spec.rb b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb new file mode 100644 index 0000000000..c78b32de38 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/shutdown_spec.rb @@ -0,0 +1,155 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +platform_is_not :windows do # hangs + describe "Socket::BasicSocket#shutdown" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + describe 'using an Integer' do + it 'shuts down a socket for reading' do + @client.shutdown(Socket::SHUT_RD) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for writing' do + @client.shutdown(Socket::SHUT_WR) + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for reading and writing' do + @client.shutdown(Socket::SHUT_RDWR) + + @client.recv(1).to_s.should be_empty + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown(666) }.should raise_error(ArgumentError) + end + end + + describe 'using a Symbol' do + it 'shuts down a socket for reading using :RD' do + @client.shutdown(:RD) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading using :SHUT_RD' do + @client.shutdown(:SHUT_RD) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for writing using :WR' do + @client.shutdown(:WR) + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for writing using :SHUT_WR' do + @client.shutdown(:SHUT_WR) + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for reading and writing' do + @client.shutdown(:RDWR) + + @client.recv(1).to_s.should be_empty + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown(:Nope) }.should raise_error(SocketError) + end + end + + describe 'using a String' do + it 'shuts down a socket for reading using "RD"' do + @client.shutdown('RD') + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading using "SHUT_RD"' do + @client.shutdown('SHUT_RD') + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for writing using "WR"' do + @client.shutdown('WR') + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'shuts down a socket for writing using "SHUT_WR"' do + @client.shutdown('SHUT_WR') + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + + it 'raises ArgumentError when using an invalid option' do + -> { @server.shutdown('Nope') }.should raise_error(SocketError) + end + end + + describe 'using an object that responds to #to_str' do + before do + @dummy = mock(:dummy) + end + + it 'shuts down a socket for reading using "RD"' do + @dummy.stub!(:to_str).and_return('RD') + + @client.shutdown(@dummy) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading using "SHUT_RD"' do + @dummy.stub!(:to_str).and_return('SHUT_RD') + + @client.shutdown(@dummy) + + @client.recv(1).to_s.should be_empty + end + + it 'shuts down a socket for reading and writing' do + @dummy.stub!(:to_str).and_return('RDWR') + + @client.shutdown(@dummy) + + @client.recv(1).to_s.should be_empty + + -> { @client.write('hello') }.should raise_error(Errno::EPIPE) + end + end + + describe 'using an object that does not respond to #to_str' do + it 'raises TypeError' do + -> { @server.shutdown(mock(:dummy)) }.should raise_error(TypeError) + end + end + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb new file mode 100644 index 0000000000..523e732959 --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#write_nonblock" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.connect(@r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "sends data" do + @w.write_nonblock("aaa").should == 3 + IO.select([@r], nil, nil, 2) + @r.recv_nonblock(5).should == "aaa" + end + + platform_is :linux do + it 'does not set the IO in nonblock mode' do + require 'io/nonblock' + @w.nonblock = false + @w.write_nonblock("aaa").should == 3 + @w.should_not.nonblock? + end + end + + platform_is_not :linux, :windows do + it 'sets the IO in nonblock mode' do + require 'io/nonblock' + @w.nonblock = false + @w.write_nonblock("aaa").should == 3 + @w.should.nonblock? + end + end + end +end diff --git a/spec/ruby/library/socket/constants/constants_spec.rb b/spec/ruby/library/socket/constants/constants_spec.rb new file mode 100644 index 0000000000..b9a9d42725 --- /dev/null +++ b/spec/ruby/library/socket/constants/constants_spec.rb @@ -0,0 +1,108 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Constants" do + it "defines socket types" do + consts = ["SOCK_DGRAM", "SOCK_RAW", "SOCK_RDM", "SOCK_SEQPACKET", "SOCK_STREAM"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines protocol families" do + consts = ["PF_INET6", "PF_INET", "PF_UNIX", "PF_UNSPEC"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :aix do + it "defines PF_IPX protocol" do + Socket::Constants.should have_constant("PF_IPX") + end + end + + it "defines address families" do + consts = ["AF_INET6", "AF_INET", "AF_UNIX", "AF_UNSPEC"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :aix do + it "defines AF_IPX address" do + Socket::Constants.should have_constant("AF_IPX") + end + end + + it "defines send/receive options" do + consts = ["MSG_DONTROUTE", "MSG_OOB", "MSG_PEEK"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines socket level options" do + consts = ["SOL_SOCKET"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines socket options" do + consts = ["SO_BROADCAST", "SO_DEBUG", "SO_DONTROUTE", "SO_ERROR", "SO_KEEPALIVE", "SO_LINGER", + "SO_OOBINLINE", "SO_RCVBUF", "SO_REUSEADDR", "SO_SNDBUF", "SO_TYPE"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + it "defines multicast options" do + consts = ["IP_ADD_MEMBERSHIP", + "IP_MULTICAST_LOOP", "IP_MULTICAST_TTL"] + platform_is_not :windows do + consts += ["IP_DEFAULT_MULTICAST_LOOP", "IP_DEFAULT_MULTICAST_TTL"] + end + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :windows, :aix, :android do + it "defines multicast options" do + consts = ["IP_MAX_MEMBERSHIPS"] + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + end + + it "defines TCP options" do + consts = ["TCP_NODELAY"] + platform_is_not :windows do + consts << "TCP_MAXSEG" + end + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + + platform_is_not :windows do + it 'defines SCM options' do + Socket::Constants.should have_constant('SCM_RIGHTS') + end + + it 'defines error options' do + consts = ["EAI_ADDRFAMILY", "EAI_NODATA"] + + # FreeBSD (11.1, at least) obsoletes EAI_ADDRFAMILY and EAI_NODATA + platform_is :freebsd do + consts = %w(EAI_MEMORY) + end + + consts.each do |c| + Socket::Constants.should have_constant(c) + end + end + end +end diff --git a/spec/ruby/library/socket/fixtures/classes.rb b/spec/ruby/library/socket/fixtures/classes.rb new file mode 100644 index 0000000000..786629d2ef --- /dev/null +++ b/spec/ruby/library/socket/fixtures/classes.rb @@ -0,0 +1,166 @@ +require 'socket' + +module SocketSpecs + # helper to get the hostname associated to 127.0.0.1 or the given ip + def self.hostname(ip = "127.0.0.1") + # Calculate each time, without caching, since the result might + # depend on things like do_not_reverse_lookup mode, which is + # changing from test to test + Socket.getaddrinfo(ip, nil)[0][2] + end + + def self.hostname_reverse_lookup(ip = "127.0.0.1") + Socket.getaddrinfo(ip, nil, 0, 0, 0, 0, true)[0][2] + end + + def self.addr(which=:ipv4) + case which + when :ipv4 + host = "127.0.0.1" + when :ipv6 + host = "::1" + end + Socket.getaddrinfo(host, nil)[0][3] + end + + def self.reserved_unused_port + # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers + 0 + end + + def self.sockaddr_in(port, host) + Socket::SockAddr_In.new(Socket.sockaddr_in(port, host)) + end + + def self.socket_path + path = tmp("unix.sock", false) + # Check for too long unix socket path (max 104 bytes on macOS) + # Note that Linux accepts not null-terminated paths but the man page advises against it. + if path.bytesize > 104 + # rm_r in spec/mspec/lib/mspec/helpers/fs.rb fails against + # "/tmp/unix_server_spec.socket" + skip "too long unix socket path: #{path}" + end + rm_socket(path) + path + end + + def self.rm_socket(path) + File.delete(path) if File.exist?(path) + end + + def self.ipv6_available? + @ipv6_available ||= begin + server = TCPServer.new('::1', 0) + rescue Errno::EAFNOSUPPORT, Errno::EADDRNOTAVAIL, SocketError + :no + else + server.close + :yes + end + @ipv6_available == :yes + end + + def self.each_ip_protocol + describe 'using IPv4' do + yield Socket::AF_INET, '127.0.0.1', 'AF_INET' + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using IPv6' do + yield Socket::AF_INET6, '::1', 'AF_INET6' + end + end + end + + def self.loop_with_timeout(timeout = TIME_TOLERANCE) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + while yield == :retry + if Process.clock_gettime(Process::CLOCK_MONOTONIC) - start >= timeout + raise RuntimeError, "Did not succeed within #{timeout} seconds" + end + end + end + + def self.dest_addr_req_error + error = Errno::EDESTADDRREQ + platform_is :windows do + error = Errno::ENOTCONN + end + error + end + + # TCPServer echo server accepting one connection + class SpecTCPServer + attr_reader :hostname, :port + + def initialize + @hostname = SocketSpecs.hostname + @server = TCPServer.new @hostname, 0 + @port = @server.addr[1] + + log "SpecTCPServer starting on #{@hostname}:#{@port}" + + @thread = Thread.new do + socket = @server.accept + log "SpecTCPServer accepted connection: #{socket}" + service socket + end + end + + def service(socket) + begin + data = socket.recv(1024) + + return if data.nil? || data.empty? + log "SpecTCPServer received: #{data.inspect}" + + return if data == "QUIT" + + socket.send data, 0 + ensure + socket.close + end + end + + def shutdown + log "SpecTCPServer shutting down" + @thread.join + @server.close + end + + def log(message) + @logger.puts message if @logger + end + end + + # We need to find a free port for Socket.tcp_server_loop and Socket.udp_server_loop, + # and the only reliable way to do that is to pass 0 as the port, but then we need to + # find out which one was chosen and the API doesn't let us find what it is. So we + # intercept one of the public API methods called by these methods. + class ServerLoopPortFinder < Socket + def self.tcp_server_sockets(*args) + super(*args) { |sockets| + @port = sockets.first.local_address.ip_port + yield(sockets) + } + end + + def self.udp_server_sockets(*args, &block) + super(*args) { |sockets| + @port = sockets.first.local_address.ip_port + yield(sockets) + } + end + + def self.cleanup + @port = nil + end + + def self.port + sleep 0.001 until @port + @port + end + end +end diff --git a/spec/ruby/library/socket/fixtures/send_io.txt b/spec/ruby/library/socket/fixtures/send_io.txt new file mode 100644 index 0000000000..eaaa1eb3ec --- /dev/null +++ b/spec/ruby/library/socket/fixtures/send_io.txt @@ -0,0 +1 @@ +This data is magic. diff --git a/spec/ruby/library/socket/ipsocket/addr_spec.rb b/spec/ruby/library/socket/ipsocket/addr_spec.rb new file mode 100644 index 0000000000..199eb85ab7 --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/addr_spec.rb @@ -0,0 +1,105 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#addr" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @socket = TCPServer.new("127.0.0.1", 0) + end + + after :each do + @socket.close unless @socket.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it "returns an array with the socket's information" do + @socket.do_not_reverse_lookup = false + BasicSocket.do_not_reverse_lookup = false + addrinfo = @socket.addr + addrinfo[0].should == "AF_INET" + addrinfo[1].should be_kind_of(Integer) + addrinfo[2].should == SocketSpecs.hostname + addrinfo[3].should == "127.0.0.1" + end + + it "returns an address in the array if do_not_reverse_lookup is true" do + @socket.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + addrinfo = @socket.addr + addrinfo[0].should == "AF_INET" + addrinfo[1].should be_kind_of(Integer) + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end + + it "returns an address in the array if passed false" do + addrinfo = @socket.addr(false) + addrinfo[0].should == "AF_INET" + addrinfo[1].should be_kind_of(Integer) + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end +end + +describe 'Socket::IPSocket#addr' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'without reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil)[0][2] + end + + it 'returns an Array containing address information' do + @server.addr.should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'with reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + end + + describe 'using true as the argument' do + it 'returns an Array containing address information' do + @server.addr(true).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :hostname as the argument' do + it 'returns an Array containing address information' do + @server.addr(:hostname).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :cats as the argument' do + it 'raises ArgumentError' do + -> { @server.addr(:cats) }.should raise_error(ArgumentError) + end + end + end + + describe 'with do_not_reverse_lookup disabled on socket level' do + before do + @server.do_not_reverse_lookup = false + + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + end + + after do + @server.do_not_reverse_lookup = true + end + + it 'returns an Array containing address information' do + @server.addr.should == [family_name, @port, @hostname, ip_address] + end + end + end +end diff --git a/spec/ruby/library/socket/ipsocket/getaddress_spec.rb b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb new file mode 100644 index 0000000000..329f8267d3 --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#getaddress" do + it "returns the IP address of hostname" do + addr_local = IPSocket.getaddress(SocketSpecs.hostname) + ["127.0.0.1", "::1"].include?(addr_local).should == true + end + + it "returns the IP address when passed an IP" do + IPSocket.getaddress("127.0.0.1").should == "127.0.0.1" + IPSocket.getaddress("0.0.0.0").should == "0.0.0.0" + IPSocket.getaddress('::1').should == '::1' + end + + it 'returns IPv4 compatible IPv6 addresses' do + IPSocket.getaddress('::ffff:192.168.1.1').should == '::ffff:192.168.1.1' + end + + # There is no way to make this fail-proof on all machines, because + # DNS servers like opendns return A records for ANY host, including + # traditionally invalidly named ones. + it "raises an error on unknown hostnames" do + -> { + IPSocket.getaddress("rubyspecdoesntexist.ruby-lang.org") + }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb b/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb new file mode 100644 index 0000000000..702650940b --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/peeraddr_spec.rb @@ -0,0 +1,117 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#peeraddr" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", @port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it "raises error if socket is not connected" do + -> { + @server.peeraddr + }.should raise_error(Errno::ENOTCONN) + end + + it "returns an array of information on the peer" do + @client.do_not_reverse_lookup = false + BasicSocket.do_not_reverse_lookup = false + addrinfo = @client.peeraddr + addrinfo[0].should == "AF_INET" + addrinfo[1].should == @port + addrinfo[2].should == SocketSpecs.hostname + addrinfo[3].should == "127.0.0.1" + end + + it "returns an IP instead of hostname if do_not_reverse_lookup is true" do + @client.do_not_reverse_lookup = true + BasicSocket.do_not_reverse_lookup = true + addrinfo = @client.peeraddr + addrinfo[0].should == "AF_INET" + addrinfo[1].should == @port + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end + + it "returns an IP instead of hostname if passed false" do + addrinfo = @client.peeraddr(false) + addrinfo[0].should == "AF_INET" + addrinfo[1].should == @port + addrinfo[2].should == "127.0.0.1" + addrinfo[3].should == "127.0.0.1" + end +end + +describe 'Socket::IPSocket#peeraddr' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + @client = TCPSocket.new(ip_address, @port) + end + + after do + @client.close + @server.close + end + + describe 'without reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil)[0][2] + end + + it 'returns an Array containing address information' do + @client.peeraddr.should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'with reverse lookups' do + before do + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + end + + describe 'using true as the argument' do + it 'returns an Array containing address information' do + @client.peeraddr(true).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :hostname as the argument' do + it 'returns an Array containing address information' do + @client.peeraddr(:hostname).should == [family_name, @port, @hostname, ip_address] + end + end + + describe 'using :cats as the argument' do + it 'raises ArgumentError' do + -> { @client.peeraddr(:cats) }.should raise_error(ArgumentError) + end + end + end + + describe 'with do_not_reverse_lookup disabled on socket level' do + before do + @client.do_not_reverse_lookup = false + + @hostname = Socket.getaddrinfo(ip_address, nil, nil, 0, 0, 0, true)[0][2] + @port = @client.local_address.ip_port + end + + after do + @client.do_not_reverse_lookup = true + end + + it 'returns an Array containing address information' do + @client.addr.should == [family_name, @port, @hostname, ip_address] + end + end + end +end diff --git a/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb new file mode 100644 index 0000000000..b58903df23 --- /dev/null +++ b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb @@ -0,0 +1,205 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::IPSocket#recvfrom" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", @port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "reads data from the connection" do + data = nil + t = Thread.new do + client = @server.accept + begin + data = client.recvfrom(6) + ensure + client.close + end + end + + @client.send('hello', 0) + @client.shutdown rescue nil + # shutdown may raise Errno::ENOTCONN when sent data is pending. + t.join + + data.first.should == 'hello' + end + + it "reads up to len bytes" do + data = nil + t = Thread.new do + client = @server.accept + begin + data = client.recvfrom(3) + ensure + client.close + end + end + + @client.send('hello', 0) + @client.shutdown rescue nil + t.join + + data.first.should == 'hel' + end + + it "returns an array with the data and connection info" do + data = nil + t = Thread.new do + client = @server.accept + data = client.recvfrom(3) + client.close + end + + @client.send('hello', 0) + @client.shutdown rescue nil + t.join + + data.size.should == 2 + data.first.should == "hel" + # This does not apply to every platform, dependent on recvfrom(2) + # data.last.should == nil + end +end + +describe "Socket::IPSocket#recvfrom" do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + port = @server.addr[1] + @client = TCPSocket.new("127.0.0.1", port) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String as received data on a closed stream socket" do + t = Thread.new do + client = @server.accept + message = client.recvfrom(10) + message + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.close + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + message = client.recvfrom(10) + message + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.close + + t.value.should be_nil + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns an empty String as received data" do + @server.bind(ip_address, 0) + addr = @server.connect_address + @client.connect(addr.ip_address, addr.ip_port) + + @client.send('', 0) + message = @server.recvfrom(1) + + message.should.is_a? Array + message[0].should == "" + end + end + end + end +end + +describe 'Socket::IPSocket#recvfrom' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + + @server.bind(ip_address, 0) + @client.connect(ip_address, @server.connect_address.ip_port) + + @hostname = Socket.getaddrinfo(ip_address, nil)[0][2] + end + + after do + @client.close + @server.close + end + + it 'returns an Array containing up to N bytes and address information' do + @client.write('hello') + + port = @client.local_address.ip_port + ret = @server.recvfrom(2) + + ret.should == ['he', [family_name, port, @hostname, ip_address]] + end + + it 'allows specifying of flags when receiving data' do + @client.write('hello') + + @server.recvfrom(2, Socket::MSG_PEEK)[0].should == 'he' + + @server.recvfrom(2)[0].should == 'he' + end + + describe 'using reverse lookups' do + before do + @server.do_not_reverse_lookup = false + + @hostname = Socket.getaddrinfo(ip_address, nil, 0, 0, 0, 0, true)[0][2] + end + + it 'includes the hostname in the address Array' do + @client.write('hello') + + port = @client.local_address.ip_port + ret = @server.recvfrom(2) + + ret.should == ['he', [family_name, port, @hostname, ip_address]] + end + end + end +end diff --git a/spec/ruby/library/socket/option/bool_spec.rb b/spec/ruby/library/socket/option/bool_spec.rb new file mode 100644 index 0000000000..144a78043d --- /dev/null +++ b/spec/ruby/library/socket/option/bool_spec.rb @@ -0,0 +1,27 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Option.bool" do + it "creates a new Socket::Option" do + so = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + so.should be_an_instance_of(Socket::Option) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + so.data.should == [1].pack('i') + end +end + +describe "Socket::Option#bool" do + it "returns boolean value" do + Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true).bool.should == true + Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, false).bool.should == false + end + + platform_is_not :windows do + it 'raises TypeError when called on a non boolean option' do + opt = Socket::Option.linger(1, 4) + -> { opt.bool }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/option/initialize_spec.rb b/spec/ruby/library/socket/option/initialize_spec.rb new file mode 100644 index 0000000000..8071ad7ef0 --- /dev/null +++ b/spec/ruby/library/socket/option/initialize_spec.rb @@ -0,0 +1,83 @@ +require_relative '../spec_helper' + +describe 'Socket::Option#initialize' do + before do + @bool = [0].pack('i') + end + + describe 'using Integers' do + it 'returns a Socket::Option' do + opt = Socket::Option + .new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, @bool) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_KEEPALIVE + opt.data.should == @bool + end + end + + describe 'using Symbols' do + it 'returns a Socket::Option' do + opt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, @bool) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_KEEPALIVE + opt.data.should == @bool + end + + it 'raises when using an invalid address family' do + -> { + Socket::Option.new(:INET2, :SOCKET, :KEEPALIVE, @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid level' do + -> { + Socket::Option.new(:INET, :CATS, :KEEPALIVE, @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid option name' do + -> { + Socket::Option.new(:INET, :SOCKET, :CATS, @bool) + }.should raise_error(SocketError) + end + end + + describe 'using Strings' do + it 'returns a Socket::Option' do + opt = Socket::Option.new('INET', 'SOCKET', 'KEEPALIVE', @bool) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::SOL_SOCKET + opt.optname.should == Socket::SO_KEEPALIVE + opt.data.should == @bool + end + + it 'raises when using an invalid address family' do + -> { + Socket::Option.new('INET2', 'SOCKET', 'KEEPALIVE', @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid level' do + -> { + Socket::Option.new('INET', 'CATS', 'KEEPALIVE', @bool) + }.should raise_error(SocketError) + end + + it 'raises when using an invalid option name' do + -> { + Socket::Option.new('INET', 'SOCKET', 'CATS', @bool) + }.should raise_error(SocketError) + end + end +end diff --git a/spec/ruby/library/socket/option/inspect_spec.rb b/spec/ruby/library/socket/option/inspect_spec.rb new file mode 100644 index 0000000000..ebea940d2f --- /dev/null +++ b/spec/ruby/library/socket/option/inspect_spec.rb @@ -0,0 +1,19 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + + +describe 'Socket::Option#inspect' do + it 'correctly returns SO_LINGER value' do + value = Socket::Option.linger(nil, 0).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER off 0sec>' + + value = Socket::Option.linger(false, 30).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER off 30sec>' + + value = Socket::Option.linger(true, 0).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER on 0sec>' + + value = Socket::Option.linger(true, 30).inspect + value.should == '#<Socket::Option: UNSPEC SOCKET LINGER on 30sec>' + end +end diff --git a/spec/ruby/library/socket/option/int_spec.rb b/spec/ruby/library/socket/option/int_spec.rb new file mode 100644 index 0000000000..8c69ef6cbd --- /dev/null +++ b/spec/ruby/library/socket/option/int_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Option.int" do + it "creates a new Socket::Option" do + so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 5) + so.should be_an_instance_of(Socket::Option) + so.family.should == Socket::Constants::AF_INET + so.level.should == Socket::Constants::SOL_SOCKET + so.optname.should == Socket::Constants::SO_KEEPALIVE + so.data.should == [5].pack('i') + end + + it 'returns a Socket::Option' do + opt = Socket::Option.int(:INET, :IP, :TTL, 4) + + opt.should be_an_instance_of(Socket::Option) + + opt.family.should == Socket::AF_INET + opt.level.should == Socket::IPPROTO_IP + opt.optname.should == Socket::IP_TTL + opt.data.should == [4].pack('i') + end +end + +describe "Socket::Option#int" do + it "returns int value" do + so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 17) + so.int.should == 17 + + so = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 32765) + so.int.should == 32765 + + Socket::Option.int(:INET, :IP, :TTL, 4).int.should == 4 + end + + platform_is_not :windows do + it 'raises TypeError when called on a non integer option' do + opt = Socket::Option.linger(1, 4) + -> { opt.int }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/option/linger_spec.rb b/spec/ruby/library/socket/option/linger_spec.rb new file mode 100644 index 0000000000..ee987db85b --- /dev/null +++ b/spec/ruby/library/socket/option/linger_spec.rb @@ -0,0 +1,76 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +option_pack = 'i*' +platform_is :windows do + option_pack = 's*' +end + +describe "Socket::Option.linger" do + it "creates a new Socket::Option for SO_LINGER" do + so = Socket::Option.linger(1, 10) + so.should be_an_instance_of(Socket::Option) + + so.family.should == Socket::Constants::AF_UNSPEC + so.level.should == Socket::Constants::SOL_SOCKET + so.optname.should == Socket::Constants::SO_LINGER + + so.data.should == [1, 10].pack(option_pack) + end + + it "accepts boolean as onoff argument" do + so = Socket::Option.linger(false, 0) + so.data.should == [0, 0].pack(option_pack) + + so = Socket::Option.linger(true, 1) + so.data.should == [1, 1].pack(option_pack) + end +end + +describe "Socket::Option#linger" do + it "returns linger option" do + so = Socket::Option.linger(0, 5) + ary = so.linger + ary[0].should be_false + ary[1].should == 5 + + so = Socket::Option.linger(false, 4) + ary = so.linger + ary[0].should be_false + ary[1].should == 4 + + so = Socket::Option.linger(1, 10) + ary = so.linger + ary[0].should be_true + ary[1].should == 10 + + so = Socket::Option.linger(true, 9) + ary = so.linger + ary[0].should be_true + ary[1].should == 9 + end + + it "raises TypeError if not a SO_LINGER" do + so = Socket::Option.int(:AF_UNSPEC, :SOL_SOCKET, :KEEPALIVE, 1) + -> { so.linger }.should raise_error(TypeError) + end + + it 'raises TypeError when called on a non SOL_SOCKET/SO_LINGER option' do + opt = Socket::Option.int(:INET, :IP, :TTL, 4) + + -> { opt.linger }.should raise_error(TypeError) + end + + platform_is_not :windows do + it "raises TypeError if option has not good size" do + so = Socket::Option.int(:AF_UNSPEC, :SOL_SOCKET, :LINGER, 1) + -> { so.linger }.should raise_error(TypeError) + end + end + + it 'raises TypeError when called on a non linger option' do + opt = Socket::Option.new(:INET, :SOCKET, :LINGER, '') + + -> { opt.linger }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/socket/option/new_spec.rb b/spec/ruby/library/socket/option/new_spec.rb new file mode 100644 index 0000000000..a9e6f09097 --- /dev/null +++ b/spec/ruby/library/socket/option/new_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::Option.new" do + it "should accept integers" do + so = Socket::Option.new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [0].pack('i')) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + end + + it "should accept symbols" do + so = Socket::Option.new(:AF_INET, :SOL_SOCKET, :SO_KEEPALIVE, [0].pack('i')) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + + so = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [0].pack('i')) + so.family.should == Socket::AF_INET + so.level.should == Socket::SOL_SOCKET + so.optname.should == Socket::SO_KEEPALIVE + end + + it "should raise error on unknown family" do + -> { Socket::Option.new(:INET4, :SOCKET, :KEEPALIVE, [0].pack('i')) }.should raise_error(SocketError) + end + + it "should raise error on unknown level" do + -> { Socket::Option.new(:INET, :ROCKET, :KEEPALIVE, [0].pack('i')) }.should raise_error(SocketError) + end + + it "should raise error on unknown option name" do + -> { Socket::Option.new(:INET, :SOCKET, :ALIVE, [0].pack('i')) }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/shared/address.rb b/spec/ruby/library/socket/shared/address.rb new file mode 100644 index 0000000000..49ba17c400 --- /dev/null +++ b/spec/ruby/library/socket/shared/address.rb @@ -0,0 +1,259 @@ +require_relative '../fixtures/classes' + +describe :socket_local_remote_address, shared: true do + describe 'using TCPSocket' do + before :each do + @s = TCPServer.new('127.0.0.1', 0) + @a = TCPSocket.new('127.0.0.1', @s.addr[1]) + @b = @s.accept + @addr = @object.call(@a) + end + + after :each do + [@b, @a, @s].each(&:close) + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == Socket::AF_INET + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.1' + end + + it 'uses the correct port' do + if @method == :local_address + @addr.ip_port.should != @s.addr[1] + else + @addr.ip_port.should == @s.addr[1] + end + end + + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using IPv6' do + before :each do + @s = TCPServer.new('::1', 0) + @a = TCPSocket.new('::1', @s.addr[1]) + @b = @s.accept + @addr = @object.call(@a) + end + + after :each do + [@b, @a, @s].each(&:close) + end + + it 'uses AF_INET6 as the address family' do + @addr.afamily.should == Socket::AF_INET6 + end + + it 'uses PF_INET6 as the protocol family' do + @addr.pfamily.should == Socket::PF_INET6 + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '::1' + end + + it 'uses the correct port' do + if @method == :local_address + @addr.ip_port.should != @s.addr[1] + else + @addr.ip_port.should == @s.addr[1] + end + end + + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end + end + + describe 'using UNIXSocket' do + before :each do + @path = SocketSpecs.socket_path + @s = UNIXServer.new(@path) + @a = UNIXSocket.new(@path) + @b = @s.accept + @addr = @object.call(@a) + end + + after :each do + [@b, @a, @s].each(&:close) + rm_r(@path) + end + + it 'uses AF_UNIX as the address family' do + @addr.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @addr.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct socket path' do + if @method == :local_address + @addr.unix_path.should == "" + else + @addr.unix_path.should == @path + end + end + + platform_is_not :windows do + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it 'equals address of peer socket' do + if @method == :local_address + @addr.to_s.should == @b.remote_address.to_s + else + @addr.to_s.should == @b.local_address.to_s + end + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the server' do + skip if @method == :local_address + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end + + describe 'using UDPSocket' do + before :each do + @s = UDPSocket.new + @s.bind("127.0.0.1", 0) + @a = UDPSocket.new + @a.connect("127.0.0.1", @s.addr[1]) + @addr = @object.call(@a) + end + + after :each do + [@a, @s].each(&:close) + end + + it 'uses the correct address family' do + @addr.afamily.should == Socket::AF_INET + end + + it 'uses the correct protocol family' do + @addr.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_DGRAM as the socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the correct IP address' do + @addr.ip_address.should == '127.0.0.1' + end + + it 'uses the correct port' do + if @method == :local_address + @addr.ip_port.should != @s.addr[1] + else + @addr.ip_port.should == @s.addr[1] + end + end + + it 'returns an Addrinfo' do + @addr.should be_an_instance_of(Addrinfo) + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'can be used to connect to the peer' do + b = @addr.connect + begin + b.remote_address.to_s.should == @addr.to_s + ensure + b.close + end + end + end +end diff --git a/spec/ruby/library/socket/shared/pack_sockaddr.rb b/spec/ruby/library/socket/shared/pack_sockaddr.rb new file mode 100644 index 0000000000..4bfcf4edb9 --- /dev/null +++ b/spec/ruby/library/socket/shared/pack_sockaddr.rb @@ -0,0 +1,92 @@ +# coding: utf-8 +describe :socket_pack_sockaddr_in, shared: true do + it "packs and unpacks" do + sockaddr_in = Socket.public_send(@method, 0, nil) + port, addr = Socket.unpack_sockaddr_in(sockaddr_in) + ["127.0.0.1", "::1"].include?(addr).should == true + port.should == 0 + + sockaddr_in = Socket.public_send(@method, 0, '') + Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '0.0.0.0'] + + sockaddr_in = Socket.public_send(@method, 80, '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, '80', '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, nil, '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, 80, Socket::INADDR_ANY) + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '0.0.0.0'] + end + + it 'resolves the service name to a port' do + sockaddr_in = Socket.public_send(@method, 'http', '127.0.0.1') + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '127.0.0.1'] + end + + describe 'using an IPv4 address' do + it 'returns a String of 16 bytes' do + str = Socket.public_send(@method, 80, '127.0.0.1') + + str.should be_an_instance_of(String) + str.bytesize.should == 16 + end + end + + describe 'using an IPv6 address' do + it 'returns a String of 28 bytes' do + str = Socket.public_send(@method, 80, '::1') + + str.should be_an_instance_of(String) + str.bytesize.should == 28 + end + end +end + +describe :socket_pack_sockaddr_un, shared: true do + it 'should be idempotent' do + bytes = Socket.public_send(@method, '/tmp/foo').bytes + bytes[2..9].should == [47, 116, 109, 112, 47, 102, 111, 111] + bytes[10..-1].all?(&:zero?).should == true + end + + it "packs and unpacks" do + sockaddr_un = Socket.public_send(@method, '/tmp/s') + Socket.unpack_sockaddr_un(sockaddr_un).should == '/tmp/s' + end + + it "handles correctly paths with multibyte chars" do + sockaddr_un = Socket.public_send(@method, '/home/вася/sock') + path = Socket.unpack_sockaddr_un(sockaddr_un).encode('UTF-8', 'UTF-8') + path.should == '/home/вася/sock' + end + + platform_is :linux do + it 'returns a String of 110 bytes' do + str = Socket.public_send(@method, '/tmp/test.sock') + + str.should be_an_instance_of(String) + str.bytesize.should == 110 + end + end + + platform_is :bsd do + it 'returns a String of 106 bytes' do + str = Socket.public_send(@method, '/tmp/test.sock') + + str.should be_an_instance_of(String) + str.bytesize.should == 106 + end + end + + platform_is_not :aix do + it "raises ArgumentError for paths that are too long" do + # AIX doesn't raise error + long_path = 'a' * 110 + -> { Socket.public_send(@method, long_path) }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/library/socket/shared/partially_closable_sockets.rb b/spec/ruby/library/socket/shared/partially_closable_sockets.rb new file mode 100644 index 0000000000..b1c2ebabe1 --- /dev/null +++ b/spec/ruby/library/socket/shared/partially_closable_sockets.rb @@ -0,0 +1,13 @@ +describe :partially_closable_sockets, shared: true do + it "if the write end is closed then the other side can read past EOF without blocking" do + @s1.write("foo") + @s1.close_write + @s2.read("foo".size + 1).should == "foo" + end + + it "closing the write end ensures that the other side can read until EOF" do + @s1.write("hello world") + @s1.close_write + @s2.read.should == "hello world" + end +end diff --git a/spec/ruby/library/socket/shared/socketpair.rb b/spec/ruby/library/socket/shared/socketpair.rb new file mode 100644 index 0000000000..25146cfff6 --- /dev/null +++ b/spec/ruby/library/socket/shared/socketpair.rb @@ -0,0 +1,138 @@ +describe :socket_socketpair, shared: true do + platform_is_not :windows do + it "ensures the returned sockets are connected" do + s1, s2 = Socket.public_send(@method, Socket::AF_UNIX, 1, 0) + s1.puts("test") + s2.gets.should == "test\n" + s1.close + s2.close + end + + it "responses with array of two sockets" do + begin + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + ensure + s1.close + s2.close + end + end + + describe 'using an Integer as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.public_send(@method, Socket::AF_UNIX, Socket::SOCK_STREAM) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + end + + describe 'using a Symbol as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'raises SocketError for an unknown address family' do + -> { Socket.public_send(@method, :CATS, :STREAM) }.should raise_error(SocketError) + end + + it 'raises SocketError for an unknown socket type' do + -> { Socket.public_send(@method, :UNIX, :CATS) }.should raise_error(SocketError) + end + end + + describe 'using a String as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.public_send(@method, 'UNIX', 'STREAM') + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'raises SocketError for an unknown address family' do + -> { Socket.public_send(@method, 'CATS', 'STREAM') }.should raise_error(SocketError) + end + + it 'raises SocketError for an unknown socket type' do + -> { Socket.public_send(@method, 'UNIX', 'CATS') }.should raise_error(SocketError) + end + end + + describe 'using an object that responds to #to_str as the 1st and 2nd argument' do + it 'returns two Socket objects' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('UNIX') + type.stub!(:to_str).and_return('STREAM') + + s1, s2 = Socket.public_send(@method, family, type) + + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'raises TypeError when #to_str does not return a String' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return(Socket::AF_UNIX) + type.stub!(:to_str).and_return(Socket::SOCK_STREAM) + + -> { Socket.public_send(@method, family, type) }.should raise_error(TypeError) + end + + it 'raises SocketError for an unknown address family' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('CATS') + type.stub!(:to_str).and_return('STREAM') + + -> { Socket.public_send(@method, family, type) }.should raise_error(SocketError) + end + + it 'raises SocketError for an unknown socket type' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('UNIX') + type.stub!(:to_str).and_return('CATS') + + -> { Socket.public_send(@method, family, type) }.should raise_error(SocketError) + end + end + + it 'accepts a custom protocol as an Integer as the 3rd argument' do + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM, Socket::IPPROTO_IP) + s1.should be_an_instance_of(Socket) + s2.should be_an_instance_of(Socket) + s1.close + s2.close + end + + it 'connects the returned Socket objects' do + s1, s2 = Socket.public_send(@method, :UNIX, :STREAM) + begin + s1.write('hello') + s2.recv(5).should == 'hello' + ensure + s1.close + s2.close + end + end + end +end diff --git a/spec/ruby/library/socket/socket/accept_loop_spec.rb b/spec/ruby/library/socket/socket/accept_loop_spec.rb new file mode 100644 index 0000000000..78e8c3fa4a --- /dev/null +++ b/spec/ruby/library/socket/socket/accept_loop_spec.rb @@ -0,0 +1,84 @@ +require_relative '../spec_helper' + +describe 'Socket.accept_loop' do + before do + @server = Socket.new(:INET, :STREAM) + @client = Socket.new(:INET, :STREAM) + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + @server.listen(1) + end + + after do + @client.close + @server.close + end + + describe 'using an Array of Sockets' do + describe 'without any available connections' do + # FIXME windows randomly hangs here forever + # https://ci.appveyor.com/project/ruby/ruby/builds/20817932/job/dor2ipny7ru4erpa + platform_is_not :windows do + it 'blocks the caller' do + -> { Socket.accept_loop([@server]) }.should block_caller + end + end + end + + describe 'with available connections' do + before do + @client.connect(@server.getsockname) + end + + it 'yields a Socket and an Addrinfo' do + conn = nil + addr = nil + + Socket.accept_loop([@server]) do |connection, address| + conn = connection + addr = address + break + end + + begin + conn.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + ensure + conn.close + end + end + end + end + + describe 'using separate Socket arguments' do + describe 'without any available connections' do + it 'blocks the caller' do + -> { Socket.accept_loop(@server) }.should block_caller + end + end + + describe 'with available connections' do + before do + @client.connect(@server.getsockname) + end + + it 'yields a Socket and an Addrinfo' do + conn = nil + addr = nil + + Socket.accept_loop(@server) do |connection, address| + conn = connection + addr = address + break + end + + begin + conn.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + ensure + conn.close + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/accept_nonblock_spec.rb b/spec/ruby/library/socket/socket/accept_nonblock_spec.rb new file mode 100644 index 0000000000..011622988c --- /dev/null +++ b/spec/ruby/library/socket/socket/accept_nonblock_spec.rb @@ -0,0 +1,141 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#accept_nonblock" do + before :each do + @hostname = "127.0.0.1" + @addr = Socket.sockaddr_in(0, @hostname) + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + @socket.bind(@addr) + @socket.listen(1) + end + + after :each do + @socket.close + end + + it "raises IO::WaitReadable if the connection is not accepted yet" do + -> { + @socket.accept_nonblock + }.should raise_error(IO::WaitReadable) { |e| + platform_is_not :windows do + e.should be_kind_of(Errno::EAGAIN) + end + platform_is :windows do + e.should be_kind_of(Errno::EWOULDBLOCK) + end + } + end + + it 'returns :wait_readable in exceptionless mode' do + @socket.accept_nonblock(exception: false).should == :wait_readable + end +end + +describe 'Socket#accept_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM, 0) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @server.close unless @server.closed? + end + + describe 'using an unbound socket' do + it 'raises Errno::EINVAL' do + -> { @server.accept_nonblock }.should raise_error(Errno::EINVAL) + -> { @server.accept_nonblock(exception: false) }.should raise_error(Errno::EINVAL) + end + end + + describe "using a bound socket that's not listening" do + before do + @server.bind(@sockaddr) + end + + it 'raises Errno::EINVAL' do + -> { @server.accept_nonblock }.should raise_error(Errno::EINVAL) + -> { @server.accept_nonblock(exception: false) }.should raise_error(Errno::EINVAL) + end + end + + describe 'using a closed socket' do + it 'raises IOError' do + @server.close + + -> { @server.accept_nonblock }.should raise_error(IOError) + -> { @server.accept_nonblock(exception: false) }.should raise_error(IOError) + end + end + + describe "using a bound socket that's listening" do + before do + @server.bind(@sockaddr) + @server.listen(1) + end + + describe 'without a connected client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + end + + platform_is_not :windows do + describe 'with a connected client' do + before do + addr = Socket.sockaddr_in(@server.local_address.ip_port, ip_address) + @client = Socket.new(family, :STREAM, 0) + + @client.connect(addr) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns an Array containing a Socket and an Addrinfo' do + IO.select([@server]) + @socket, addrinfo = @server.accept_nonblock + + @socket.should be_an_instance_of(Socket) + addrinfo.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + IO.select([@server]) + @socket, @addr = @server.accept_nonblock + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the same IP address as the client Socket' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the same port as the client Socket' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/accept_spec.rb b/spec/ruby/library/socket/socket/accept_spec.rb new file mode 100644 index 0000000000..417f996c55 --- /dev/null +++ b/spec/ruby/library/socket/socket/accept_spec.rb @@ -0,0 +1,121 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#accept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM, 0) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @server.close unless @server.closed? + end + + platform_is :linux do # hangs on other platforms + describe 'using an unbound socket' do + it 'raises Errno::EINVAL' do + -> { @server.accept }.should raise_error(Errno::EINVAL) + end + end + + describe "using a bound socket that's not listening" do + before do + @server.bind(@sockaddr) + end + + it 'raises Errno::EINVAL' do + -> { @server.accept }.should raise_error(Errno::EINVAL) + end + end + end + + describe 'using a closed socket' do + it 'raises IOError' do + @server.close + + -> { @server.accept }.should raise_error(IOError) + end + end + + describe "using a bound socket that's listening" do + before do + @server.bind(@sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, ip_address) + end + + describe 'without a connected client' do + it 'blocks the caller until a connection is available' do + client = Socket.new(family, :STREAM, 0) + thread = Thread.new do + @server.accept + end + + client.connect(@server_addr) + + value = thread.value + begin + value.should be_an_instance_of(Array) + ensure + client.close + value[0].close + end + end + end + + describe 'with a connected client' do + before do + addr = Socket.sockaddr_in(@server.local_address.ip_port, ip_address) + @client = Socket.new(family, :STREAM, 0) + + @client.connect(addr) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns an Array containing a Socket and an Addrinfo' do + @socket, addrinfo = @server.accept + + @socket.should be_an_instance_of(Socket) + addrinfo.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + before do + @socket, @addr = @server.accept + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @addr.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the same IP address as the client Socket' do + @addr.ip_address.should == @client.local_address.ip_address + end + + it 'uses the same port as the client Socket' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/bind_spec.rb b/spec/ruby/library/socket/socket/bind_spec.rb new file mode 100644 index 0000000000..e76336eafa --- /dev/null +++ b/spec/ruby/library/socket/socket/bind_spec.rb @@ -0,0 +1,150 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#bind on SOCK_DGRAM socket" do + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) + @sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1") + end + + after :each do + @sock.closed?.should be_false + @sock.close + end + + it "binds to a port" do + -> { @sock.bind(@sockaddr) }.should_not raise_error + end + + it "returns 0 if successful" do + @sock.bind(@sockaddr).should == 0 + end + + it "raises Errno::EINVAL when already bound" do + @sock.bind(@sockaddr) + + -> { @sock.bind(@sockaddr) }.should raise_error(Errno::EINVAL) + end + + it "raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available from the local machine" do + sockaddr1 = Socket.pack_sockaddr_in(0, "4.3.2.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + platform_is_not :windows, :cygwin do + as_user do + break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil + it "raises Errno::EACCES when the current user does not have permission to bind" do + sockaddr1 = Socket.pack_sockaddr_in(1, "127.0.0.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EACCES) + end + end + end +end + +describe "Socket#bind on SOCK_STREAM socket" do + before :each do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + @sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1") + end + + after :each do + @sock.closed?.should be_false + @sock.close + end + + it "binds to a port" do + -> { @sock.bind(@sockaddr) }.should_not raise_error + end + + it "returns 0 if successful" do + @sock.bind(@sockaddr).should == 0 + end + + it "raises Errno::EINVAL when already bound" do + @sock.bind(@sockaddr) + + -> { @sock.bind(@sockaddr) }.should raise_error(Errno::EINVAL) + end + + it "raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available from the local machine" do + sockaddr1 = Socket.pack_sockaddr_in(0, "4.3.2.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + platform_is_not :windows, :cygwin do + as_user do + break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil + it "raises Errno::EACCES when the current user does not have permission to bind" do + sockaddr1 = Socket.pack_sockaddr_in(1, "127.0.0.1") + -> { @sock.bind(sockaddr1) }.should raise_error(Errno::EACCES) + end + end + end +end + +describe 'Socket#bind' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a packed socket address' do + before do + @socket = Socket.new(family, :DGRAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @socket.close + end + + it 'returns 0 when successfully bound' do + @socket.bind(@sockaddr).should == 0 + end + + it 'raises Errno::EINVAL when binding to an already bound port' do + @socket.bind(@sockaddr) + + -> { @socket.bind(@sockaddr) }.should raise_error(Errno::EINVAL) + end + + it 'raises Errno::EADDRNOTAVAIL when the specified sockaddr is not available' do + ip = family == Socket::AF_INET ? '4.3.2.1' : '::2' + sockaddr1 = Socket.sockaddr_in(0, ip) + + -> { @socket.bind(sockaddr1) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + platform_is_not :windows do + as_user do + break if File.read('/proc/sys/net/ipv4/ip_unprivileged_port_start').to_i <= 1 rescue nil + + it 'raises Errno::EACCES when the user is not allowed to bind to the port' do + sockaddr1 = Socket.pack_sockaddr_in(1, ip_address) + + -> { @socket.bind(sockaddr1) }.should raise_error(Errno::EACCES) + end + end + end + end + + describe 'using an Addrinfo' do + before do + @addr = Addrinfo.udp(ip_address, 0) + @socket = Socket.new(@addr.afamily, @addr.socktype) + end + + after do + @socket.close + end + + it 'binds to an Addrinfo' do + @socket.bind(@addr).should == 0 + @socket.local_address.should be_an_instance_of(Addrinfo) + end + + it 'uses a new Addrinfo for the local address' do + @socket.bind(@addr) + @socket.local_address.should_not == @addr + end + end + end +end diff --git a/spec/ruby/library/socket/socket/connect_nonblock_spec.rb b/spec/ruby/library/socket/socket/connect_nonblock_spec.rb new file mode 100644 index 0000000000..359b8719fb --- /dev/null +++ b/spec/ruby/library/socket/socket/connect_nonblock_spec.rb @@ -0,0 +1,147 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#connect_nonblock" do + before :each do + @hostname = "127.0.0.1" + @server = TCPServer.new(@hostname, 0) # started, but no accept + @addr = Socket.sockaddr_in(@server.addr[1], @hostname) + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + @thread = nil + end + + after :each do + @socket.close + @server.close + @thread.join if @thread + end + + it "connects the socket to the remote side" do + port = nil + accept = false + @thread = Thread.new do + server = TCPServer.new(@hostname, 0) + port = server.addr[1] + Thread.pass until accept + conn = server.accept + conn << "hello!" + conn.close + server.close + end + + Thread.pass until port + + addr = Socket.sockaddr_in(port, @hostname) + begin + @socket.connect_nonblock(addr) + rescue Errno::EINPROGRESS + end + + accept = true + IO.select nil, [@socket] + + begin + @socket.connect_nonblock(addr) + rescue Errno::EISCONN + # Not all OS's use this errno, so we trap and ignore it + end + + @socket.read(6).should == "hello!" + end + + platform_is_not :freebsd, :aix do + it "raises Errno::EINPROGRESS when the connect would block" do + -> do + @socket.connect_nonblock(@addr) + end.should raise_error(Errno::EINPROGRESS) + end + + it "raises Errno::EINPROGRESS with IO::WaitWritable mixed in when the connect would block" do + -> do + @socket.connect_nonblock(@addr) + end.should raise_error(IO::WaitWritable) + end + + it "returns :wait_writable in exceptionless mode when the connect would block" do + @socket.connect_nonblock(@addr, exception: false).should == :wait_writable + end + end +end + +describe 'Socket#connect_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a DGRAM socket' do + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + + @server.bind(@sockaddr) + end + + after do + @client.close + @server.close + end + + it 'returns 0 when successfully connected using a String' do + @client.connect_nonblock(@server.getsockname).should == 0 + end + + it 'returns 0 when successfully connected using an Addrinfo' do + @client.connect_nonblock(@server.connect_address).should == 0 + end + + it 'raises TypeError when passed an Integer' do + -> { @client.connect_nonblock(666) }.should raise_error(TypeError) + end + end + + describe 'using a STREAM socket' do + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + it 'raises Errno::EISCONN when already connected' do + @server.listen(1) + @client.connect(@server.connect_address).should == 0 + + -> { + @client.connect_nonblock(@server.connect_address) + + # A second call needed if non-blocking sockets become default + # XXX honestly I don't expect any real code to care about this spec + # as it's too implementation-dependent and checking for connect() + # errors is futile anyways because of TOCTOU + @client.connect_nonblock(@server.connect_address) + }.should raise_error(Errno::EISCONN) + end + + it 'returns 0 when already connected in exceptionless mode' do + @server.listen(1) + @client.connect(@server.connect_address).should == 0 + + @client.connect_nonblock(@server.connect_address, exception: false).should == 0 + end + end + + platform_is_not :freebsd do + it 'raises IO:EINPROGRESSWaitWritable when the connection would block' do + @server.bind(@sockaddr) + + -> { + @client.connect_nonblock(@server.connect_address) + }.should raise_error(IO::EINPROGRESSWaitWritable) + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/connect_spec.rb b/spec/ruby/library/socket/socket/connect_spec.rb new file mode 100644 index 0000000000..130379ce2b --- /dev/null +++ b/spec/ruby/library/socket/socket/connect_spec.rb @@ -0,0 +1,78 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#connect' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + it 'returns 0 when connected successfully using a String' do + @server.listen(1) + + @client.connect(@server.getsockname).should == 0 + end + + it 'returns 0 when connected successfully using an Addrinfo' do + @server.listen(1) + + @client.connect(@server.connect_address).should == 0 + end + + it 'raises Errno::EISCONN when already connected' do + @server.listen(1) + + @client.connect(@server.getsockname).should == 0 + + -> { + @client.connect(@server.getsockname) + + # A second call needed if non-blocking sockets become default + # XXX honestly I don't expect any real code to care about this spec + # as it's too implementation-dependent and checking for connect() + # errors is futile anyways because of TOCTOU + @client.connect(@server.getsockname) + }.should raise_error(Errno::EISCONN) + end + + platform_is_not :darwin do + it 'raises Errno::ECONNREFUSED or Errno::ETIMEDOUT when the connection failed' do + begin + @client.connect(@server.getsockname) + rescue => e + [Errno::ECONNREFUSED, Errno::ETIMEDOUT].include?(e.class).should == true + end + end + end + end + + ruby_version_is "3.4" do + it "fails with timeout" do + # TEST-NET-1 IP address are reserved for documentation and example purposes. + address = Socket.pack_sockaddr_in(1, "192.0.2.1") + + client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) + client.timeout = 0 + + -> { + begin + client.connect(address) + rescue Errno::ECONNREFUSED + skip "Outgoing packets may be filtered" + rescue Errno::ENETUNREACH + skip "Off line" + end + }.should raise_error(IO::TimeoutError) + ensure + client.close + end + end +end diff --git a/spec/ruby/library/socket/socket/for_fd_spec.rb b/spec/ruby/library/socket/socket/for_fd_spec.rb new file mode 100644 index 0000000000..e89228d436 --- /dev/null +++ b/spec/ruby/library/socket/socket/for_fd_spec.rb @@ -0,0 +1,30 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.for_fd" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + @client = TCPSocket.open("127.0.0.1", @port) + end + + after :each do + @socket.close + @client.close + @host.close + @server.close + end + + it "creates a new Socket that aliases the existing Socket's file descriptor" do + @socket = Socket.for_fd(@client.fileno) + @socket.autoclose = false + @socket.fileno.should == @client.fileno + + @socket.send("foo", 0) + @client.send("bar", 0) + + @host = @server.accept + @host.read(3).should == "foo" + @host.read(3).should == "bar" + end +end diff --git a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb new file mode 100644 index 0000000000..6576af52ee --- /dev/null +++ b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb @@ -0,0 +1,391 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.getaddrinfo" do + before :each do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = true + end + + after :each do + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + platform_is_not :windows do + it "gets the address information" do + expected = [] + # The check for AP_INET6's class is needed because ipaddr.rb adds + # fake AP_INET6 even in case when IPv6 is not really supported. + # Without such check, this test might fail when ipaddr was required + # by some other specs. + if (Socket.constants.include? 'AF_INET6') && + (Socket::AF_INET6.class != Object) then + expected.concat [ + ['AF_INET6', 9, SocketSpecs.hostname, '::1', Socket::AF_INET6, + Socket::SOCK_DGRAM, Socket::IPPROTO_UDP], + ['AF_INET6', 9, SocketSpecs.hostname, '::1', Socket::AF_INET6, + Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ['AF_INET6', 9, SocketSpecs.hostname, 'fe80::1%lo0', Socket::AF_INET6, + Socket::SOCK_DGRAM, Socket::IPPROTO_UDP], + ['AF_INET6', 9, SocketSpecs.hostname, 'fe80::1%lo0', Socket::AF_INET6, + Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ] + end + + expected.concat [ + ['AF_INET', 9, SocketSpecs.hostname, '127.0.0.1', Socket::AF_INET, + Socket::SOCK_DGRAM, Socket::IPPROTO_UDP], + ['AF_INET', 9, SocketSpecs.hostname, '127.0.0.1', Socket::AF_INET, + Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ] + + addrinfo = Socket.getaddrinfo SocketSpecs.hostname, 'discard' + addrinfo.each do |a| + case a.last + when Socket::IPPROTO_UDP, Socket::IPPROTO_TCP + expected.should include(a) + else + # don't check this. It's some weird protocol we don't know about + # so we can't spec it. + end + end + end + + # #getaddrinfo will return a INADDR_ANY address (0.0.0.0 or "::") + # if it's a passive socket. In the case of non-passive + # sockets (AI_PASSIVE not set) it should return the loopback + # address (127.0.0.1 or "::1"). + + it "accepts empty addresses for IPv4 passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + Socket::AI_PASSIVE) + + expected = [["AF_INET", 9, "0.0.0.0", "0.0.0.0", Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]] + res.should == expected + end + + it "accepts empty addresses for IPv4 non-passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + 0) + + expected = [["AF_INET", 9, "127.0.0.1", "127.0.0.1", Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]] + res.should == expected + end + + + it "accepts empty addresses for IPv6 passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET6, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + Socket::AI_PASSIVE) + + expected = [ + ["AF_INET6", 9, "::", "::", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ["AF_INET6", 9, "0:0:0:0:0:0:0:0", "0:0:0:0:0:0:0:0", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP] + ] + res.each { |a| expected.should include(a) } + end + + it "accepts empty addresses for IPv6 non-passive sockets" do + res = Socket.getaddrinfo(nil, "discard", + Socket::AF_INET6, + Socket::SOCK_STREAM, + Socket::IPPROTO_TCP, + 0) + + expected = [ + ["AF_INET6", 9, "::1", "::1", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP], + ["AF_INET6", 9, "0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:1", Socket::AF_INET6, Socket::SOCK_STREAM, Socket::IPPROTO_TCP] + ] + res.each { |a| expected.should include(a) } + end + + ruby_version_is ""..."3.3" do + it "raises SocketError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(SocketError) + end + end + + ruby_version_is "3.3" do + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(Socket::ResolutionError) { |e| + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) + } + end + end + end +end + +describe 'Socket.getaddrinfo' do + describe 'without global reverse lookups' do + it 'returns an Array' do + Socket.getaddrinfo(nil, 'ftp').should be_an_instance_of(Array) + end + + it 'accepts an Integer as the address family' do + array = Socket.getaddrinfo(nil, 'ftp', Socket::AF_INET)[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an Integer as the address family using IPv6' do + array = Socket.getaddrinfo(nil, 'ftp', Socket::AF_INET6)[0] + + array[0].should == 'AF_INET6' + array[1].should == 21 + array[2].should == '::1' + array[3].should == '::1' + array[4].should == Socket::AF_INET6 + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a Symbol as the address family' do + array = Socket.getaddrinfo(nil, 'ftp', :INET)[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a Symbol as the address family using IPv6' do + array = Socket.getaddrinfo(nil, 'ftp', :INET6)[0] + + array[0].should == 'AF_INET6' + array[1].should == 21 + array[2].should == '::1' + array[3].should == '::1' + array[4].should == Socket::AF_INET6 + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a String as the address family' do + array = Socket.getaddrinfo(nil, 'ftp', 'INET')[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts a String as the address family using IPv6' do + array = Socket.getaddrinfo(nil, 'ftp', 'INET6')[0] + + array[0].should == 'AF_INET6' + array[1].should == 21 + array[2].should == '::1' + array[3].should == '::1' + array[4].should == Socket::AF_INET6 + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an object responding to #to_str as the host' do + dummy = mock(:dummy) + + dummy.stub!(:to_str).and_return('127.0.0.1') + + array = Socket.getaddrinfo(dummy, 'ftp')[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an object responding to #to_str as the address family' do + dummy = mock(:dummy) + + dummy.stub!(:to_str).and_return('INET') + + array = Socket.getaddrinfo(nil, 'ftp', dummy)[0] + + array[0].should == 'AF_INET' + array[1].should == 21 + array[2].should == '127.0.0.1' + array[3].should == '127.0.0.1' + array[4].should == Socket::AF_INET + array[5].should be_kind_of(Integer) + array[6].should be_kind_of(Integer) + end + + it 'accepts an Integer as the socket type' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, Socket::SOCK_STREAM)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'accepts a Symbol as the socket type' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'accepts a String as the socket type' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, 'STREAM')[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'accepts an object responding to #to_str as the socket type' do + dummy = mock(:dummy) + + dummy.stub!(:to_str).and_return('STREAM') + + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, dummy)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + platform_is_not :windows do + it 'accepts an Integer as the protocol family' do + *array, proto = Socket.getaddrinfo(nil, 'discard', :INET, :DGRAM, Socket::IPPROTO_UDP)[0] + array.should == [ + 'AF_INET', + 9, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_DGRAM, + ] + [0, Socket::IPPROTO_UDP].should include(proto) + end + end + + it 'accepts an Integer as the flags' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, Socket::AI_PASSIVE)[0] + array.should == [ + 'AF_INET', + 21, + '0.0.0.0', + '0.0.0.0', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + + it 'performs a reverse lookup when the reverse_lookup argument is true' do + addr = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, 0, true)[0] + + addr[0].should == 'AF_INET' + addr[1].should == 21 + + addr[2].should be_an_instance_of(String) + addr[2].should_not == addr[3] + + addr[3].should == '127.0.0.1' + end + + it 'performs a reverse lookup when the reverse_lookup argument is :hostname' do + addr = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, 0, :hostname)[0] + + addr[0].should == 'AF_INET' + addr[1].should == 21 + + addr[2].should be_an_instance_of(String) + addr[2].should_not == addr[3] + + addr[3].should == '127.0.0.1' + end + + it 'performs a reverse lookup when the reverse_lookup argument is :numeric' do + *array, proto = Socket.getaddrinfo(nil, 'ftp', :INET, :STREAM, + Socket::IPPROTO_TCP, 0, :numeric)[0] + array.should == [ + 'AF_INET', + 21, + '127.0.0.1', + '127.0.0.1', + Socket::AF_INET, + Socket::SOCK_STREAM, + ] + [0, Socket::IPPROTO_TCP].should include(proto) + end + end + + describe 'with global reverse lookups' do + before do + @do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = false + end + + after do + BasicSocket.do_not_reverse_lookup = @do_not_reverse_lookup + end + + it 'returns an address honoring the global lookup option' do + addr = Socket.getaddrinfo(nil, 'ftp', :INET)[0] + + addr[0].should == 'AF_INET' + addr[1].should == 21 + + # We don't have control over this value and there's no way to test this + # without relying on Socket.getaddrinfo()'s own behaviour (meaning this + # test would faily any way of the method was not implemented correctly). + addr[2].should be_an_instance_of(String) + addr[2].should_not == addr[3] + + addr[3].should == '127.0.0.1' + end + end +end diff --git a/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb b/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb new file mode 100644 index 0000000000..5d936046f5 --- /dev/null +++ b/spec/ruby/library/socket/socket/gethostbyaddr_spec.rb @@ -0,0 +1,121 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require 'ipaddr' + +describe 'Socket.gethostbyaddr' do + describe 'using an IPv4 address' do + before do + @addr = IPAddr.new('127.0.0.1').hton + end + + describe 'without an explicit address family' do + it 'returns an Array' do + suppress_warning { Socket.gethostbyaddr(@addr) }.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = suppress_warning { Socket.gethostbyaddr(@addr) } + end + + it 'includes the hostname as the first value' do + @array[0].should == SocketSpecs.hostname_reverse_lookup + end + + it 'includes the aliases as the 2nd value' do + @array[1].should be_an_instance_of(Array) + + @array[1].each do |val| + val.should be_an_instance_of(String) + end + end + + it 'includes the address type as the 3rd value' do + @array[2].should == Socket::AF_INET + end + + it 'includes all address strings as the remaining values' do + @array[3].should == @addr + + @array[4..-1].each do |val| + val.should be_an_instance_of(String) + end + end + end + end + + describe 'with an explicit address family' do + it 'returns an Array when using an Integer as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, Socket::AF_INET) }.should be_an_instance_of(Array) + end + + it 'returns an Array when using a Symbol as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, :INET) }.should be_an_instance_of(Array) + end + + it 'raises SocketError when the address is not supported by the family' do + -> { suppress_warning { Socket.gethostbyaddr(@addr, :INET6) } }.should raise_error(SocketError) + end + end + end + + guard -> { SocketSpecs.ipv6_available? && platform_is_not(:aix) } do + describe 'using an IPv6 address' do + before do + @addr = IPAddr.new('::1').hton + end + + describe 'without an explicit address family' do + it 'returns an Array' do + suppress_warning { Socket.gethostbyaddr(@addr) }.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = suppress_warning { Socket.gethostbyaddr(@addr) } + end + + it 'includes the hostname as the first value' do + @array[0].should == SocketSpecs.hostname_reverse_lookup("::1") + end + + it 'includes the aliases as the 2nd value' do + @array[1].should be_an_instance_of(Array) + + @array[1].each do |val| + val.should be_an_instance_of(String) + end + end + + it 'includes the address type as the 3rd value' do + @array[2].should == Socket::AF_INET6 + end + + it 'includes all address strings as the remaining values' do + @array[3].should be_an_instance_of(String) + + @array[4..-1].each do |val| + val.should be_an_instance_of(String) + end + end + end + end + + describe 'with an explicit address family' do + it 'returns an Array when using an Integer as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, Socket::AF_INET6) }.should be_an_instance_of(Array) + end + + it 'returns an Array when using a Symbol as the address family' do + suppress_warning { Socket.gethostbyaddr(@addr, :INET6) }.should be_an_instance_of(Array) + end + + platform_is_not :windows, :wsl do + it 'raises SocketError when the address is not supported by the family' do + -> { suppress_warning { Socket.gethostbyaddr(@addr, :INET) } }.should raise_error(SocketError) + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/gethostbyname_spec.rb b/spec/ruby/library/socket/socket/gethostbyname_spec.rb new file mode 100644 index 0000000000..618ef85387 --- /dev/null +++ b/spec/ruby/library/socket/socket/gethostbyname_spec.rb @@ -0,0 +1,135 @@ +# encoding: binary +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.gethostbyname" do + it "returns broadcast address info for '<broadcast>'" do + addr = suppress_warning { Socket.gethostbyname('<broadcast>') } + addr.should == ["255.255.255.255", [], 2, "\xFF\xFF\xFF\xFF"] + end + + it "returns broadcast address info for '<any>'" do + addr = suppress_warning { Socket.gethostbyname('<any>') } + addr.should == ["0.0.0.0", [], 2, "\x00\x00\x00\x00"] + end +end + +describe 'Socket.gethostbyname' do + it 'returns an Array' do + suppress_warning { Socket.gethostbyname('127.0.0.1') }.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = suppress_warning { Socket.gethostbyname('127.0.0.1') } + end + + it 'includes the hostname as the first value' do + @array[0].should == '127.0.0.1' + end + + it 'includes the aliases as the 2nd value' do + @array[1].should be_an_instance_of(Array) + + @array[1].each do |val| + val.should be_an_instance_of(String) + end + end + + it 'includes the address type as the 3rd value' do + possible = [Socket::AF_INET, Socket::AF_INET6] + + possible.include?(@array[2]).should == true + end + + it 'includes the address strings as the remaining values' do + @array[3].should be_an_instance_of(String) + + @array[4..-1].each do |val| + val.should be_an_instance_of(String) + end + end + end + + describe 'using <broadcast> as the input address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('<broadcast>') } + end + + it 'includes the broadcast address as the first value' do + @addr[0].should == '255.255.255.255' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [255, 255, 255, 255].pack('C4') + end + end + end + + describe 'using <any> as the input address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('<any>') } + end + + it 'includes the wildcard address as the first value' do + @addr[0].should == '0.0.0.0' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [0, 0, 0, 0].pack('C4') + end + end + end + + describe 'using an IPv4 address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('127.0.0.1') } + end + + it 'includes the IP address as the first value' do + @addr[0].should == '127.0.0.1' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [127, 0, 0, 1].pack('C4') + end + end + end + + guard -> { SocketSpecs.ipv6_available? } do + describe 'using an IPv6 address' do + describe 'the returned Array' do + before do + @addr = suppress_warning { Socket.gethostbyname('::1') } + end + + it 'includes the IP address as the first value' do + @addr[0].should == '::1' + end + + it 'includes the address type as the 3rd value' do + @addr[2].should == Socket::AF_INET6 + end + + it 'includes the address string as the 4th value' do + @addr[3].should == [0, 0, 0, 0, 0, 0, 0, 1].pack('n8') + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/gethostname_spec.rb b/spec/ruby/library/socket/socket/gethostname_spec.rb new file mode 100644 index 0000000000..dfca7cf5cd --- /dev/null +++ b/spec/ruby/library/socket/socket/gethostname_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.gethostname" do + def system_hostname + if platform_is_not :windows + # `uname -n` is the most portable way to get the hostname, as it is a POSIX standard: + `uname -n`.strip + else + # Windows does not have uname, so we use hostname instead: + `hostname`.strip + end + end + + it "returns the host name" do + Socket.gethostname.should == system_hostname + end +end diff --git a/spec/ruby/library/socket/socket/getifaddrs_spec.rb b/spec/ruby/library/socket/socket/getifaddrs_spec.rb new file mode 100644 index 0000000000..839854ea27 --- /dev/null +++ b/spec/ruby/library/socket/socket/getifaddrs_spec.rb @@ -0,0 +1,117 @@ +require_relative '../spec_helper' + +platform_is_not :aix do +describe 'Socket.getifaddrs' do + before do + @ifaddrs = Socket.getifaddrs + end + + it 'returns an Array' do + @ifaddrs.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + it 'should not be empty' do + @ifaddrs.should_not be_empty + end + + it 'contains instances of Socket::Ifaddr' do + @ifaddrs.each do |ifaddr| + ifaddr.should be_an_instance_of(Socket::Ifaddr) + end + end + end + + describe 'each returned Socket::Ifaddr' do + it 'has an interface index' do + @ifaddrs.each do |ifaddr| + ifaddr.ifindex.should be_kind_of(Integer) + end + end + + it 'has an interface name' do + @ifaddrs.each do |ifaddr| + ifaddr.name.should be_an_instance_of(String) + end + end + + it 'has a set of flags' do + @ifaddrs.each do |ifaddr| + ifaddr.flags.should be_kind_of(Integer) + end + end + end + + describe 'the Socket::Ifaddr address' do + before do + @addrs = @ifaddrs.map(&:addr).compact + end + + it 'is an Addrinfo' do + @addrs.all? do |addr| + addr.should be_an_instance_of(Addrinfo) + true + end.should be_true + end + + it 'has an address family' do + @addrs.all? do |addr| + addr.afamily.should be_kind_of(Integer) + addr.afamily.should_not == Socket::AF_UNSPEC + true + end.should be_true + end + end + + platform_is_not :windows do + describe 'the Socket::Ifaddr broadcast address' do + before do + @addrs = @ifaddrs.map(&:broadaddr).compact + end + + it 'is an Addrinfo' do + @addrs.all? do |addr| + addr.should be_an_instance_of(Addrinfo) + true + end.should be_true + end + + it 'has an address family' do + @addrs.all? do |addr| + addr.afamily.should be_kind_of(Integer) + addr.afamily.should_not == Socket::AF_UNSPEC + true + end.should be_true + end + end + + describe 'the Socket::Ifaddr netmask address' do + before do + @addrs = @ifaddrs.map(&:netmask).compact.select(&:ip?) + end + + it 'is an Addrinfo' do + @addrs.all? do |addr| + addr.should be_an_instance_of(Addrinfo) + true + end.should be_true + end + + it 'has an address family' do + @addrs.all? do |addr| + addr.afamily.should be_kind_of(Integer) + addr.afamily.should_not == Socket::AF_UNSPEC + true + end.should be_true + end + + it 'has an IP address' do + @addrs.all? do |addr| + addr.ip_address.should be_an_instance_of(String) + true + end.should be_true + end + end + end +end +end diff --git a/spec/ruby/library/socket/socket/getnameinfo_spec.rb b/spec/ruby/library/socket/socket/getnameinfo_spec.rb new file mode 100644 index 0000000000..af4a10c9c2 --- /dev/null +++ b/spec/ruby/library/socket/socket/getnameinfo_spec.rb @@ -0,0 +1,165 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.getnameinfo" do + before :each do + @reverse_lookup = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = true + end + + after :each do + BasicSocket.do_not_reverse_lookup = @reverse_lookup + end + + it "gets the name information and don't resolve it" do + sockaddr = Socket.sockaddr_in 3333, '127.0.0.1' + name_info = Socket.getnameinfo(sockaddr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV) + name_info.should == ['127.0.0.1', "3333"] + end + + def should_be_valid_dns_name(name) + # http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address + # ftp://ftp.rfc-editor.org/in-notes/rfc3696.txt + # http://domainkeys.sourceforge.net/underscore.html + valid_dns = /^(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\-_]*[a-zA-Z0-9_])\.)*([A-Za-z_]|[A-Za-z_][A-Za-z0-9\-_]*[A-Za-z0-9_])\.?$/ + name.should =~ valid_dns + end + + it "gets the name information and resolve the host" do + sockaddr = Socket.sockaddr_in 3333, '127.0.0.1' + name_info = Socket.getnameinfo(sockaddr, Socket::NI_NUMERICSERV) + should_be_valid_dns_name(name_info[0]) + name_info[1].should == 3333.to_s + end + + it "gets the name information and resolves the service" do + sockaddr = Socket.sockaddr_in 9, '127.0.0.1' + name_info = Socket.getnameinfo(sockaddr) + name_info.size.should == 2 + should_be_valid_dns_name(name_info[0]) + # see http://www.iana.org/assignments/port-numbers + name_info[1].should == 'discard' + end + + it "gets a 3-element array and doesn't resolve hostname" do + name_info = Socket.getnameinfo(["AF_INET", 3333, '127.0.0.1'], Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV) + name_info.should == ['127.0.0.1', "3333"] + end + + it "gets a 3-element array and resolves the service" do + name_info = Socket.getnameinfo ["AF_INET", 9, '127.0.0.1'] + name_info[1].should == 'discard' + end + + it "gets a 4-element array and doesn't resolve hostname" do + name_info = Socket.getnameinfo(["AF_INET", 3333, 'foo', '127.0.0.1'], Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV) + name_info.should == ['127.0.0.1', "3333"] + end + + it "gets a 4-element array and resolves the service" do + name_info = Socket.getnameinfo ["AF_INET", 9, 'foo', '127.0.0.1'] + name_info[1].should == 'discard' + end + + ruby_version_is ""..."3.3" do + it "raises SocketError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(SocketError) + end + end + + ruby_version_is "3.3" do + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(Socket::ResolutionError) { |e| + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) + } + end + end +end + +describe 'Socket.getnameinfo' do + describe 'using a String as the first argument' do + before do + @addr = Socket.sockaddr_in(21, '127.0.0.1') + end + + it 'raises SocketError or TypeError when using an invalid String' do + -> { Socket.getnameinfo('cats') }.should raise_error(Exception) { |e| + (e.is_a?(SocketError) || e.is_a?(TypeError)).should == true + } + end + + describe 'without custom flags' do + it 'returns an Array containing the hostname and service name' do + Socket.getnameinfo(@addr).should == [SocketSpecs.hostname_reverse_lookup, 'ftp'] + end + end + + describe 'using NI_NUMERICHOST as the flag' do + it 'returns an Array containing the numeric hostname and service name' do + array = Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST) + + %w{127.0.0.1 ::1}.include?(array[0]).should == true + + array[1].should == 'ftp' + end + end + end + + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @hostname = SocketSpecs.hostname_reverse_lookup(ip_address) + end + + describe 'using a 3 element Array as the first argument' do + before do + @addr = [family_name, 21, @hostname] + end + + it 'raises ArgumentError when using an invalid Array' do + -> { Socket.getnameinfo([family_name]) }.should raise_error(ArgumentError) + end + + platform_is_not :windows do + describe 'using NI_NUMERICHOST as the flag' do + it 'returns an Array containing the numeric hostname and service name' do + Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST).should == [ip_address, 'ftp'] + end + end + end + end + + describe 'using a 4 element Array as the first argument' do + before do + @addr = [family_name, 21, ip_address, ip_address] + end + + describe 'without custom flags' do + it 'returns an Array containing the hostname and service name' do + array = Socket.getnameinfo(@addr) + array.should be_an_instance_of(Array) + array[0].should == @hostname + array[1].should == 'ftp' + end + + it 'uses the 3rd value as the hostname if the 4th is not present' do + addr = [family_name, 21, ip_address, nil] + + array = Socket.getnameinfo(addr) + array.should be_an_instance_of(Array) + array[0].should == @hostname + array[1].should == 'ftp' + end + end + + describe 'using NI_NUMERICHOST as the flag' do + it 'returns an Array containing the numeric hostname and service name' do + Socket.getnameinfo(@addr, Socket::NI_NUMERICHOST).should == [ip_address, 'ftp'] + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/getservbyname_spec.rb b/spec/ruby/library/socket/socket/getservbyname_spec.rb new file mode 100644 index 0000000000..d361e619f2 --- /dev/null +++ b/spec/ruby/library/socket/socket/getservbyname_spec.rb @@ -0,0 +1,32 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#getservbyname" do + it "returns the port for service 'discard'" do + Socket.getservbyname('discard').should == 9 + end + + it "returns the port for service 'discard' with protocol 'tcp'" do + Socket.getservbyname('discard', 'tcp').should == 9 + end + + it 'returns the port for service "ftp"' do + Socket.getservbyname('ftp').should == 21 + end + + it 'returns the port for service "ftp" with protocol "tcp"' do + Socket.getservbyname('ftp', 'tcp').should == 21 + end + + it "returns the port for service 'domain' with protocol 'udp'" do + Socket.getservbyname('domain', 'udp').should == 53 + end + + it "returns the port for service 'daytime'" do + Socket.getservbyname('daytime').should == 13 + end + + it "raises a SocketError when the service or port is invalid" do + -> { Socket.getservbyname('invalid') }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/socket/getservbyport_spec.rb b/spec/ruby/library/socket/socket/getservbyport_spec.rb new file mode 100644 index 0000000000..563c592b54 --- /dev/null +++ b/spec/ruby/library/socket/socket/getservbyport_spec.rb @@ -0,0 +1,23 @@ +require_relative '../spec_helper' + +describe 'Socket.getservbyport' do + platform_is_not :windows do + it 'returns the service name as a String' do + Socket.getservbyport(514).should == 'shell' + end + end + + platform_is :windows do + it 'returns the service name as a String' do + Socket.getservbyport(514).should == 'cmd' + end + end + + it 'returns the service name when using a custom protocol name' do + Socket.getservbyport(514, 'udp').should == 'syslog' + end + + it 'raises SocketError for an unknown port number' do + -> { Socket.getservbyport(0) }.should raise_error(SocketError) + end +end diff --git a/spec/ruby/library/socket/socket/initialize_spec.rb b/spec/ruby/library/socket/socket/initialize_spec.rb new file mode 100644 index 0000000000..f8337bcaa5 --- /dev/null +++ b/spec/ruby/library/socket/socket/initialize_spec.rb @@ -0,0 +1,87 @@ +require_relative '../spec_helper' + +describe 'Socket#initialize' do + before do + @socket = nil + end + + after do + @socket.close if @socket + end + + describe 'using an Integer as the 1st and 2nd arguments' do + it 'returns a Socket' do + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'using Symbols as the 1st and 2nd arguments' do + it 'returns a Socket' do + @socket = Socket.new(:INET, :STREAM) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'using Strings as the 1st and 2nd arguments' do + it 'returns a Socket' do + @socket = Socket.new('INET', 'STREAM') + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'using objects that respond to #to_str' do + it 'returns a Socket' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return('AF_INET') + type.stub!(:to_str).and_return('STREAM') + + @socket = Socket.new(family, type) + + @socket.should be_an_instance_of(Socket) + end + + it 'raises TypeError when the #to_str method does not return a String' do + family = mock(:family) + type = mock(:type) + + family.stub!(:to_str).and_return(Socket::AF_INET) + type.stub!(:to_str).and_return(Socket::SOCK_STREAM) + + -> { Socket.new(family, type) }.should raise_error(TypeError) + end + end + + describe 'using a custom protocol' do + it 'returns a Socket when using an Integer' do + @socket = Socket.new(:INET, :STREAM, Socket::IPPROTO_TCP) + + @socket.should be_an_instance_of(Socket) + end + + it 'raises TypeError when using a Symbol' do + -> { Socket.new(:INET, :STREAM, :TCP) }.should raise_error(TypeError) + end + end + + it 'sets the do_not_reverse_lookup option' do + @socket = Socket.new(:INET, :STREAM) + + @socket.do_not_reverse_lookup.should == Socket.do_not_reverse_lookup + end + + it "sets basic IO accessors" do + @socket = Socket.new(:INET, :STREAM) + @socket.lineno.should == 0 + end + + it "sets the socket to binary mode" do + @socket = Socket.new(:INET, :STREAM) + @socket.binmode?.should be_true + end +end diff --git a/spec/ruby/library/socket/socket/ip_address_list_spec.rb b/spec/ruby/library/socket/socket/ip_address_list_spec.rb new file mode 100644 index 0000000000..f97c2d7f85 --- /dev/null +++ b/spec/ruby/library/socket/socket/ip_address_list_spec.rb @@ -0,0 +1,50 @@ +require_relative '../spec_helper' + +describe 'Socket.ip_address_list' do + it 'returns an Array' do + Socket.ip_address_list.should be_an_instance_of(Array) + end + + describe 'the returned Array' do + before do + @array = Socket.ip_address_list + end + + it 'is not empty' do + @array.should_not be_empty + end + + it 'contains Addrinfo objects' do + @array.each do |klass| + klass.should be_an_instance_of(Addrinfo) + end + end + end + + describe 'each returned Addrinfo' do + before do + @array = Socket.ip_address_list + end + + it 'has a non-empty IP address' do + @array.each do |addr| + addr.ip_address.should be_an_instance_of(String) + addr.ip_address.should_not be_empty + end + end + + it 'has an address family' do + families = [Socket::AF_INET, Socket::AF_INET6] + + @array.each do |addr| + families.include?(addr.afamily).should == true + end + end + + it 'uses 0 as the port number' do + @array.each do |addr| + addr.ip_port.should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb b/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb new file mode 100644 index 0000000000..4f429c089e --- /dev/null +++ b/spec/ruby/library/socket/socket/ipv6only_bang_spec.rb @@ -0,0 +1,20 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +guard -> { SocketSpecs.ipv6_available? } do + describe 'Socket#ipv6only!' do + before do + @socket = Socket.new(:INET6, :DGRAM) + end + + after do + @socket.close + end + + it 'enables IPv6 only mode' do + @socket.ipv6only! + + @socket.getsockopt(:IPV6, :V6ONLY).bool.should == true + end + end +end diff --git a/spec/ruby/library/socket/socket/listen_spec.rb b/spec/ruby/library/socket/socket/listen_spec.rb new file mode 100644 index 0000000000..4d2aedab19 --- /dev/null +++ b/spec/ruby/library/socket/socket/listen_spec.rb @@ -0,0 +1,66 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket#listen" do + before :each do + @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + end + + after :each do + @socket.closed?.should be_false + @socket.close + end + + it "verifies we can listen for incoming connections" do + sockaddr = Socket.pack_sockaddr_in(0, "127.0.0.1") + @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + @socket.bind(sockaddr) + @socket.listen(1).should == 0 + end +end + +describe 'Socket#listen' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'using a DGRAM socket' do + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + it 'raises Errno::EOPNOTSUPP or Errno::EACCES' do + -> { @server.listen(1) }.should raise_error { |e| + [Errno::EOPNOTSUPP, Errno::EACCES].should.include?(e.class) + } + end + end + + describe 'using a STREAM socket' do + before do + @server = Socket.new(family, :STREAM) + @client = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + end + + after do + @client.close + @server.close + end + + it 'returns 0' do + @server.listen(1).should == 0 + end + + it "raises when the given argument can't be coerced to an Integer" do + -> { @server.listen('cats') }.should raise_error(TypeError) + end + end + end +end diff --git a/spec/ruby/library/socket/socket/local_address_spec.rb b/spec/ruby/library/socket/socket/local_address_spec.rb new file mode 100644 index 0000000000..3687f93a0c --- /dev/null +++ b/spec/ruby/library/socket/socket/local_address_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' + +describe 'Socket#local_address' do + before do + @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @sock.local_address.afamily.should == Socket::AF_INET + end + + it 'uses PF_INET as the protocol family' do + @sock.local_address.pfamily.should == Socket::PF_INET + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses 0.0.0.0 as the IP address' do + @sock.local_address.ip_address.should == '0.0.0.0' + end + + platform_is_not :windows do + it 'uses 0 as the port' do + @sock.local_address.ip_port.should == 0 + end + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb new file mode 100644 index 0000000000..ef2a2d4ba9 --- /dev/null +++ b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket.pack_sockaddr_in" do + it_behaves_like :socket_pack_sockaddr_in, :pack_sockaddr_in +end diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb new file mode 100644 index 0000000000..1ee0bc6157 --- /dev/null +++ b/spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket#pack_sockaddr_un" do + it_behaves_like :socket_pack_sockaddr_un, :pack_sockaddr_un +end diff --git a/spec/ruby/library/socket/socket/pair_spec.rb b/spec/ruby/library/socket/socket/pair_spec.rb new file mode 100644 index 0000000000..8dd470a95e --- /dev/null +++ b/spec/ruby/library/socket/socket/pair_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/socketpair' + +describe "Socket.pair" do + it_behaves_like :socket_socketpair, :pair +end diff --git a/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb new file mode 100644 index 0000000000..01b42bcc52 --- /dev/null +++ b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb @@ -0,0 +1,219 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#recvfrom_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + @client.connect(@server.getsockname) + end + + describe 'without any data available' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable with exception: false' do + @server.recvfrom_nonblock(1, exception: false).should == :wait_readable + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + platform_is_not :windows do + it 'returns an Array containing the data and an Addrinfo' do + IO.select([@server]) + ret = @server.recvfrom_nonblock(1) + + ret.should be_an_instance_of(Array) + ret.length.should == 2 + end + end + + it "allows an output buffer as third argument" do + @client.write('hello') + + IO.select([@server]) + buffer = +'' + message, = @server.recvfrom_nonblock(5, 0, buffer) + + message.should.equal?(buffer) + buffer.should == 'hello' + end + + it "preserves the encoding of the given buffer" do + @client.write('hello') + + IO.select([@server]) + buffer = ''.encode(Encoding::ISO_8859_1) + @server.recvfrom_nonblock(5, 0, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + + describe 'the returned data' do + it 'is the same as the sent data' do + 5.times do + @client.write('hello') + + IO.select([@server]) + msg, _ = @server.recvfrom_nonblock(5) + + msg.should == 'hello' + end + end + end + + platform_is_not :windows do + describe 'the returned Array' do + before do + IO.select([@server]) + @array = @server.recvfrom_nonblock(1) + end + + it 'contains the data at index 0' do + @array[0].should == 'h' + end + + it 'contains an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + end + + describe 'the returned Addrinfo' do + before do + IO.select([@server]) + @addr = @server.recvfrom_nonblock(1)[1] + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == ip_address + end + + it 'uses the port of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end + end +end + +describe 'Socket#recvfrom_nonblock' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = Socket.new Socket::AF_INET, :STREAM, 0 + @sockaddr = Socket.sockaddr_in(0, "127.0.0.1") + @server.bind(@sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, "127.0.0.1") + + @client = Socket.new(Socket::AF_INET, :STREAM, 0) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String as received data on a closed stream socket" do + ready = false + + t = Thread.new do + client, _ = @server.accept + + Thread.pass while !ready + begin + client.recvfrom_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + ready = true + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + ready = false + + t = Thread.new do + client, _ = @server.accept + + Thread.pass while !ready + begin + client.recvfrom_nonblock(10) + rescue IO::EAGAINWaitReadable + retry + end + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + ready = true + + t.value.should be_nil + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/recvfrom_spec.rb b/spec/ruby/library/socket/socket/recvfrom_spec.rb new file mode 100644 index 0000000000..6ba39ffcaf --- /dev/null +++ b/spec/ruby/library/socket/socket/recvfrom_spec.rb @@ -0,0 +1,179 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#recvfrom' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after do + @client.close + @server.close + end + + describe 'using an unbound socket' do + it 'blocks the caller' do + -> { @server.recvfrom(1) }.should block_caller + end + end + + describe 'using a bound socket' do + before do + @server.bind(Socket.sockaddr_in(0, ip_address)) + @client.connect(@server.getsockname) + end + + describe 'without any data available' do + it 'blocks the caller' do + -> { @server.recvfrom(1) }.should block_caller + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns an Array containing the data and an Addrinfo' do + ret = @server.recvfrom(1) + + ret.should be_an_instance_of(Array) + ret.length.should == 2 + end + + describe 'the returned Array' do + before do + @array = @server.recvfrom(1) + end + + it 'contains the data at index 0' do + @array[0].should == 'h' + end + + it 'contains an Addrinfo at index 1' do + @array[1].should be_an_instance_of(Addrinfo) + end + end + + describe 'the returned Addrinfo' do + before do + @addr = @server.recvfrom(1)[1] + end + + it 'uses AF_INET as the address family' do + @addr.afamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @addr.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses PF_INET as the protocol family' do + @addr.pfamily.should == family + end + + it 'uses 0 as the protocol' do + @addr.protocol.should == 0 + end + + it 'uses the IP address of the client' do + @addr.ip_address.should == ip_address + end + + it 'uses the port of the client' do + @addr.ip_port.should == @client.local_address.ip_port + end + end + end + end + end +end + +describe 'Socket#recvfrom' do + context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do + describe "stream socket" do + before :each do + @server = Socket.new Socket::AF_INET, :STREAM, 0 + sockaddr = Socket.sockaddr_in(0, "127.0.0.1") + @server.bind(sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, "127.0.0.1") + + @client = Socket.new(Socket::AF_INET, :STREAM, 0) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + ruby_version_is ""..."3.3" do + it "returns an empty String as received data on a closed stream socket" do + t = Thread.new do + client, _ = @server.accept + client.recvfrom(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + + t.value.should.is_a? Array + t.value[0].should == "" + end + end + + ruby_version_is "3.3" do + it "returns nil on a closed stream socket" do + t = Thread.new do + client, _ = @server.accept + client.recvfrom(10) + ensure + client.close if client + end + + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil + + @client.connect(@server_addr) + @client.close + + t.value.should be_nil + end + end + end + + describe "datagram socket" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @server = Socket.new(family, :DGRAM) + @client = Socket.new(family, :DGRAM) + end + + after :each do + @server.close unless @server.closed? + @client.close unless @client.closed? + end + + it "returns an empty String as received data" do + @server.bind(Socket.sockaddr_in(0, ip_address)) + @client.connect(@server.getsockname) + + @client.send('', 0) + message = @server.recvfrom(1) + + message.should.is_a? Array + message[0].should == "" + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/remote_address_spec.rb b/spec/ruby/library/socket/socket/remote_address_spec.rb new file mode 100644 index 0000000000..24d60d7f58 --- /dev/null +++ b/spec/ruby/library/socket/socket/remote_address_spec.rb @@ -0,0 +1,54 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#remote_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + @server.listen(1) + + @host = @server.local_address.ip_address + @port = @server.local_address.ip_port + @client = Socket.new(family, :STREAM, Socket::IPPROTO_TCP) + + @client.connect(Socket.sockaddr_in(@port, @host)) + end + + after do + @client.close + @server.close + end + + it 'returns an Addrinfo' do + @client.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @client.remote_address.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @client.remote_address.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @client.remote_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @client.remote_address.ip_address.should == @host + end + + it 'uses the correct port' do + @client.remote_address.ip_port.should == @port + end + + it 'uses 0 as the protocol' do + @client.remote_address.protocol.should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/socket/sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/sockaddr_in_spec.rb new file mode 100644 index 0000000000..8ee956ac26 --- /dev/null +++ b/spec/ruby/library/socket/socket/sockaddr_in_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket#sockaddr_in" do + it_behaves_like :socket_pack_sockaddr_in, :sockaddr_in +end diff --git a/spec/ruby/library/socket/socket/sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/sockaddr_un_spec.rb new file mode 100644 index 0000000000..8922ff4d6d --- /dev/null +++ b/spec/ruby/library/socket/socket/sockaddr_un_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/pack_sockaddr' + +describe "Socket#sockaddr_un" do + it_behaves_like :socket_pack_sockaddr_un, :sockaddr_un +end diff --git a/spec/ruby/library/socket/socket/socket_spec.rb b/spec/ruby/library/socket/socket/socket_spec.rb new file mode 100644 index 0000000000..5a3d6733e0 --- /dev/null +++ b/spec/ruby/library/socket/socket/socket_spec.rb @@ -0,0 +1,38 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket" do + it "inherits from BasicSocket and IO" do + Socket.superclass.should == BasicSocket + BasicSocket.superclass.should == IO + end +end + +describe "The socket class hierarchy" do + it "has an IPSocket in parallel to Socket" do + Socket.ancestors.include?(IPSocket).should == false + IPSocket.ancestors.include?(Socket).should == false + IPSocket.superclass.should == BasicSocket + end + + it "has TCPSocket and UDPSocket subclasses of IPSocket" do + TCPSocket.superclass.should == IPSocket + UDPSocket.superclass.should == IPSocket + end + + platform_is_not :windows do + it "has a UNIXSocket in parallel to Socket" do + Socket.ancestors.include?(UNIXSocket).should == false + UNIXSocket.ancestors.include?(Socket).should == false + UNIXSocket.superclass.should == BasicSocket + end + end +end + +platform_is_not :windows do + describe "Server class hierarchy" do + it "contains UNIXServer" do + UNIXServer.superclass.should == UNIXSocket + end + end +end diff --git a/spec/ruby/library/socket/socket/socketpair_spec.rb b/spec/ruby/library/socket/socket/socketpair_spec.rb new file mode 100644 index 0000000000..551c376d49 --- /dev/null +++ b/spec/ruby/library/socket/socket/socketpair_spec.rb @@ -0,0 +1,7 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/socketpair' + +describe "Socket.socketpair" do + it_behaves_like :socket_socketpair, :socketpair +end diff --git a/spec/ruby/library/socket/socket/sysaccept_spec.rb b/spec/ruby/library/socket/socket/sysaccept_spec.rb new file mode 100644 index 0000000000..92ac21124e --- /dev/null +++ b/spec/ruby/library/socket/socket/sysaccept_spec.rb @@ -0,0 +1,91 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket#sysaccept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :STREAM) + @sockaddr = Socket.sockaddr_in(0, ip_address) + end + + after do + @server.close + end + + platform_is :linux do # hangs on other platforms + describe 'using an unbound socket' do + it 'raises Errno::EINVAL' do + -> { @server.sysaccept }.should raise_error(Errno::EINVAL) + end + end + + describe "using a bound socket that's not listening" do + before do + @server.bind(@sockaddr) + end + + it 'raises Errno::EINVAL' do + -> { @server.sysaccept }.should raise_error(Errno::EINVAL) + end + end + end + + describe "using a bound socket that's listening" do + before do + @server.bind(@sockaddr) + @server.listen(1) + + server_ip = @server.local_address.ip_port + @server_addr = Socket.sockaddr_in(server_ip, ip_address) + end + + after do + Socket.for_fd(@fd).close if @fd + end + + describe 'without a connected client' do + before do + @client = Socket.new(family, :STREAM) + end + + after do + @client.close + end + + it 'blocks the caller until a connection is available' do + thread = Thread.new do + @fd, _ = @server.sysaccept + end + + @client.connect(@server_addr) + + thread.value.should be_an_instance_of(Array) + end + end + + describe 'with a connected client' do + before do + @client = Socket.new(family, :STREAM) + @client.connect(@server.getsockname) + end + + after do + @client.close + end + + it 'returns an Array containing an Integer and an Addrinfo' do + @fd, addrinfo = @server.sysaccept + + @fd.should be_kind_of(Integer) + addrinfo.should be_an_instance_of(Addrinfo) + end + + it 'returns a new file descriptor' do + @fd, _ = @server.sysaccept + + @fd.should_not == @client.fileno + end + end + end + end +end diff --git a/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb b/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb new file mode 100644 index 0000000000..a46c6df5c6 --- /dev/null +++ b/spec/ruby/library/socket/socket/tcp_server_loop_spec.rb @@ -0,0 +1,54 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.tcp_server_loop' do + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.tcp_server_loop('127.0.0.1', 0) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = Socket.new(:INET, :STREAM) + SocketSpecs::ServerLoopPortFinder.cleanup + end + + after do + @sock.close if @sock + @client.close + end + + it 'yields a Socket and an Addrinfo' do + @sock, addr = nil + + thread = Thread.new do + SocketSpecs::ServerLoopPortFinder.tcp_server_loop('127.0.0.1', 0) do |socket, addrinfo| + @sock = socket + addr = addrinfo + + break + end + end + + port = SocketSpecs::ServerLoopPortFinder.port + + SocketSpecs.loop_with_timeout do + begin + @client.connect(Socket.sockaddr_in(port, '127.0.0.1')) + rescue SystemCallError + sleep 0.01 + :retry + end + end + + # At this point the connection has been set up but the thread may not yet + # have returned, thus we'll need to wait a little longer for it to + # complete. + thread.join + + @sock.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb b/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb new file mode 100644 index 0000000000..bd496d3015 --- /dev/null +++ b/spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb @@ -0,0 +1,39 @@ +require_relative '../spec_helper' + +describe 'Socket.tcp_server_sockets' do + describe 'without a block' do + before do + @sockets = nil + end + + after do + @sockets.each(&:close) + end + + it 'returns an Array of Socket objects' do + @sockets = Socket.tcp_server_sockets(0) + + @sockets.should be_an_instance_of(Array) + @sockets[0].should be_an_instance_of(Socket) + end + end + + describe 'with a block' do + it 'yields the sockets to the supplied block' do + Socket.tcp_server_sockets(0) do |sockets| + sockets.should be_an_instance_of(Array) + sockets[0].should be_an_instance_of(Socket) + end + end + + it 'closes all sockets after the block returns' do + sockets = nil + + Socket.tcp_server_sockets(0) { |socks| sockets = socks } + + sockets.each do |socket| + socket.should.closed? + end + end + end +end diff --git a/spec/ruby/library/socket/socket/tcp_spec.rb b/spec/ruby/library/socket/socket/tcp_spec.rb new file mode 100644 index 0000000000..faf020b1ea --- /dev/null +++ b/spec/ruby/library/socket/socket/tcp_spec.rb @@ -0,0 +1,70 @@ +require_relative '../spec_helper' + +describe 'Socket.tcp' do + before do + @server = Socket.new(:INET, :STREAM) + @client = nil + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + @server.listen(1) + + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @client.close if @client && !@client.closed? + @client = nil + + @server.close + end + + it 'returns a Socket when no block is given' do + @client = Socket.tcp(@host, @port) + + @client.should be_an_instance_of(Socket) + end + + it 'yields the Socket when a block is given' do + Socket.tcp(@host, @port) do |socket| + socket.should be_an_instance_of(Socket) + end + end + + it 'closes the Socket automatically when a block is given' do + Socket.tcp(@host, @port) do |socket| + @socket = socket + end + + @socket.should.closed? + end + + it 'binds to a local address and port when specified' do + @client = Socket.tcp(@host, @port, @host, 0) + + @client.local_address.ip_address.should == @host + + @client.local_address.ip_port.should > 0 + @client.local_address.ip_port.should_not == @port + end + + it 'raises ArgumentError when 6 arguments are provided' do + -> { + Socket.tcp(@host, @port, @host, 0, {:connect_timeout => 1}, 10) + }.should raise_error(ArgumentError) + end + + it 'connects to the server' do + @client = Socket.tcp(@host, @port) + + @client.write('hello') + + connection, _ = @server.accept + + begin + connection.recv(5).should == 'hello' + ensure + connection.close + end + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb new file mode 100644 index 0000000000..cb8c5c5587 --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb @@ -0,0 +1,47 @@ +require_relative '../spec_helper' + +describe 'Socket.udp_server_loop_on' do + before do + @server = Socket.new(:INET, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + end + + after do + @server.close + end + + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.udp_server_loop_on([@server]) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = Socket.new(:INET, :DGRAM) + end + + after do + @client.close + end + + it 'yields the message and a Socket::UDPSource' do + msg = nil + src = nil + + @client.connect(@server.getsockname) + @client.write('hello') + + Socket.udp_server_loop_on([@server]) do |message, source| + msg = message + src = source + + break + end + + msg.should == 'hello' + src.should be_an_instance_of(Socket::UDPSource) + end + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_loop_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb new file mode 100644 index 0000000000..cd22ea56cf --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb @@ -0,0 +1,59 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.udp_server_loop' do + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.udp_server_loop('127.0.0.1', 0) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = Socket.new(:INET, :DGRAM) + SocketSpecs::ServerLoopPortFinder.cleanup + end + + after do + @client.close + end + + it 'yields the message and a Socket::UDPSource' do + msg, src = nil + + thread = Thread.new do + SocketSpecs::ServerLoopPortFinder.udp_server_loop('127.0.0.1', 0) do |message, source| + msg = message + src = source + + break + end + end + + port = SocketSpecs::ServerLoopPortFinder.port + + # Because this will return even if the server is up and running (it's UDP + # after all) we'll have to write and wait until "msg" is set. + @client.connect(Socket.sockaddr_in(port, '127.0.0.1')) + + SocketSpecs.loop_with_timeout do + begin + @client.write('hello') + rescue SystemCallError + sleep 0.01 + :retry + else + unless msg + sleep 0.001 + :retry + end + end + end + + thread.join + + msg.should == 'hello' + src.should be_an_instance_of(Socket::UDPSource) + end + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_recv_spec.rb b/spec/ruby/library/socket/socket/udp_server_recv_spec.rb new file mode 100644 index 0000000000..47ed74bc03 --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_recv_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' + +describe 'Socket.udp_server_recv' do + before do + @server = Socket.new(:INET, :DGRAM) + @client = Socket.new(:INET, :DGRAM) + + @server.bind(Socket.sockaddr_in(0, '127.0.0.1')) + @client.connect(@server.getsockname) + end + + after do + @client.close + @server.close + end + + it 'yields the message and a Socket::UDPSource' do + msg = :unset + src = :unset + + @client.write('hello') + + readable, _, _ = IO.select([@server]) + readable.size.should == 1 + + Socket.udp_server_recv(readable) do |message, source| + msg = message + src = source + break + end + + msg.should == 'hello' + src.should be_an_instance_of(Socket::UDPSource) + end +end diff --git a/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb b/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb new file mode 100644 index 0000000000..f8be672612 --- /dev/null +++ b/spec/ruby/library/socket/socket/udp_server_sockets_spec.rb @@ -0,0 +1,39 @@ +require_relative '../spec_helper' + +describe 'Socket.udp_server_sockets' do + describe 'without a block' do + before do + @sockets = nil + end + + after do + @sockets.each(&:close) + end + + it 'returns an Array of Socket objects' do + @sockets = Socket.udp_server_sockets(0) + + @sockets.should be_an_instance_of(Array) + @sockets[0].should be_an_instance_of(Socket) + end + end + + describe 'with a block' do + it 'yields the sockets to the supplied block' do + Socket.udp_server_sockets(0) do |sockets| + sockets.should be_an_instance_of(Array) + sockets[0].should be_an_instance_of(Socket) + end + end + + it 'closes all sockets after the block returns' do + sockets = nil + + Socket.udp_server_sockets(0) { |socks| sockets = socks } + + sockets.each do |socket| + socket.should.closed? + end + end + end +end diff --git a/spec/ruby/library/socket/socket/unix_server_loop_spec.rb b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb new file mode 100644 index 0000000000..6192bc8bf6 --- /dev/null +++ b/spec/ruby/library/socket/socket/unix_server_loop_spec.rb @@ -0,0 +1,56 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unix_server_loop' do + before do + @path = SocketSpecs.socket_path + end + + after do + rm_r(@path) if File.file?(@path) + end + + describe 'when no connections are available' do + it 'blocks the caller' do + -> { Socket.unix_server_loop(@path) }.should block_caller + end + end + + describe 'when a connection is available' do + before do + @client = nil + end + + after do + @sock.close if @sock + @client.close if @client + end + + it 'yields a Socket and an Addrinfo' do + @sock, addr = nil + + thread = Thread.new do + Socket.unix_server_loop(@path) do |socket, addrinfo| + @sock = socket + addr = addrinfo + + break + end + end + + SocketSpecs.loop_with_timeout do + begin + @client = Socket.unix(@path) + rescue SystemCallError + sleep 0.01 + :retry + end + end + + thread.join + + @sock.should be_an_instance_of(Socket) + addr.should be_an_instance_of(Addrinfo) + end + end +end diff --git a/spec/ruby/library/socket/socket/unix_server_socket_spec.rb b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb new file mode 100644 index 0000000000..34c3b96d07 --- /dev/null +++ b/spec/ruby/library/socket/socket/unix_server_socket_spec.rb @@ -0,0 +1,46 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unix_server_socket' do + before do + @path = SocketSpecs.socket_path + end + + after do + rm_r(@path) + end + + describe 'when no block is given' do + before do + @socket = nil + end + + after do + @socket.close + end + + it 'returns a Socket' do + @socket = Socket.unix_server_socket(@path) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'when a block is given' do + it 'yields a Socket' do + Socket.unix_server_socket(@path) do |sock| + sock.should be_an_instance_of(Socket) + end + end + + it 'closes the Socket when the block returns' do + socket = nil + + Socket.unix_server_socket(@path) do |sock| + socket = sock + end + + socket.should be_an_instance_of(Socket) + end + end +end diff --git a/spec/ruby/library/socket/socket/unix_spec.rb b/spec/ruby/library/socket/socket/unix_spec.rb new file mode 100644 index 0000000000..2a5d77f96f --- /dev/null +++ b/spec/ruby/library/socket/socket/unix_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unix' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @socket = nil + end + + after do + @server.close + @socket.close if @socket + + rm_r(@path) + end + + describe 'when no block is given' do + it 'returns a Socket' do + @socket = Socket.unix(@path) + + @socket.should be_an_instance_of(Socket) + end + end + + describe 'when a block is given' do + it 'yields a Socket' do + Socket.unix(@path) do |sock| + sock.should be_an_instance_of(Socket) + end + end + + it 'closes the Socket when the block returns' do + socket = nil + + Socket.unix(@path) do |sock| + socket = sock + end + + socket.should.closed? + end + end +end diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb new file mode 100644 index 0000000000..935b5cb543 --- /dev/null +++ b/spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb @@ -0,0 +1,44 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket.unpack_sockaddr_in" do + it "decodes the host name and port number of a packed sockaddr_in" do + sockaddr = Socket.sockaddr_in 3333, '127.0.0.1' + Socket.unpack_sockaddr_in(sockaddr).should == [3333, '127.0.0.1'] + end + + it "gets the hostname and port number from a passed Addrinfo" do + addrinfo = Addrinfo.tcp('127.0.0.1', 3333) + Socket.unpack_sockaddr_in(addrinfo).should == [3333, '127.0.0.1'] + end + + describe 'using an IPv4 address' do + it 'returns an Array containing the port and IP address' do + port = 80 + ip = '127.0.0.1' + addr = Socket.pack_sockaddr_in(port, ip) + + Socket.unpack_sockaddr_in(addr).should == [port, ip] + end + end + + describe 'using an IPv6 address' do + it 'returns an Array containing the port and IP address' do + port = 80 + ip = '::1' + addr = Socket.pack_sockaddr_in(port, ip) + + Socket.unpack_sockaddr_in(addr).should == [port, ip] + end + end + + it "raises an ArgumentError when the sin_family is not AF_INET" do + sockaddr = Socket.sockaddr_un '/tmp/x' + -> { Socket.unpack_sockaddr_in sockaddr }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed addrinfo is not AF_INET/AF_INET6" do + addrinfo = Addrinfo.unix('/tmp/sock') + -> { Socket.unpack_sockaddr_in(addrinfo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb new file mode 100644 index 0000000000..6e0f11de3d --- /dev/null +++ b/spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb @@ -0,0 +1,24 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'Socket.unpack_sockaddr_un' do + it 'decodes sockaddr to unix path' do + sockaddr = Socket.sockaddr_un('/tmp/sock') + Socket.unpack_sockaddr_un(sockaddr).should == '/tmp/sock' + end + + it 'returns unix path from a passed Addrinfo' do + addrinfo = Addrinfo.unix('/tmp/sock') + Socket.unpack_sockaddr_un(addrinfo).should == '/tmp/sock' + end + + it 'raises an ArgumentError when the sa_family is not AF_UNIX' do + sockaddr = Socket.sockaddr_in(0, '127.0.0.1') + -> { Socket.unpack_sockaddr_un(sockaddr) }.should raise_error(ArgumentError) + end + + it 'raises an ArgumentError when passed addrinfo is not AF_UNIX' do + addrinfo = Addrinfo.tcp('127.0.0.1', 0) + -> { Socket.unpack_sockaddr_un(addrinfo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/socket/spec_helper.rb b/spec/ruby/library/socket/spec_helper.rb new file mode 100644 index 0000000000..b33663e02d --- /dev/null +++ b/spec/ruby/library/socket/spec_helper.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require 'socket' + +MSpec.enable_feature :sock_packet if Socket.const_defined?(:SOCK_PACKET) +MSpec.enable_feature :udp_cork if Socket.const_defined?(:UDP_CORK) +MSpec.enable_feature :tcp_cork if Socket.const_defined?(:TCP_CORK) +MSpec.enable_feature :pktinfo if Socket.const_defined?(:IP_PKTINFO) +MSpec.enable_feature :ipv6_pktinfo if Socket.const_defined?(:IPV6_PKTINFO) +MSpec.enable_feature :ip_mtu if Socket.const_defined?(:IP_MTU) +MSpec.enable_feature :ipv6_nexthop if Socket.const_defined?(:IPV6_NEXTHOP) +MSpec.enable_feature :tcp_info if Socket.const_defined?(:TCP_INFO) +MSpec.enable_feature :ancillary_data if Socket.const_defined?(:AncillaryData) diff --git a/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb new file mode 100644 index 0000000000..91f6a327f0 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb @@ -0,0 +1,85 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "Socket::TCPServer.accept_nonblock" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + it "accepts non blocking connections" do + @server.listen(5) + -> { + @server.accept_nonblock + }.should raise_error(IO::WaitReadable) + + c = TCPSocket.new("127.0.0.1", @port) + IO.select([@server]) + s = @server.accept_nonblock + + port, address = Socket.unpack_sockaddr_in(s.getsockname) + + port.should == @port + address.should == "127.0.0.1" + s.should be_kind_of(TCPSocket) + + c.close + s.close + end + + it "raises an IOError if the socket is closed" do + @server.close + -> { @server.accept }.should raise_error(IOError) + end + + describe 'without a connected client' do + it 'raises error' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable in exceptionless mode' do + @server.accept_nonblock(exception: false).should == :wait_readable + end + end +end + +describe 'TCPServer#accept_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + describe 'without a connected client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + end + + platform_is_not :windows do # spurious + describe 'with a connected client' do + before do + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns a TCPSocket' do + IO.select([@server]) + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(TCPSocket) + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/accept_spec.rb b/spec/ruby/library/socket/tcpserver/accept_spec.rb new file mode 100644 index 0000000000..d8892cd5f0 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/accept_spec.rb @@ -0,0 +1,132 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer#accept" do + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + it "accepts a connection and returns a TCPSocket" do + data = nil + t = Thread.new do + client = @server.accept + client.should be_kind_of(TCPSocket) + data = client.read(5) + client << "goodbye" + client.close + end + Thread.pass while t.status and t.status != "sleep" + + socket = TCPSocket.new('127.0.0.1', @port) + socket.write('hello') + socket.shutdown(1) # we are done with sending + socket.read.should == 'goodbye' + t.join + data.should == 'hello' + socket.close + end + + it "can be interrupted by Thread#kill" do + t = Thread.new { @server.accept } + + Thread.pass while t.status and t.status != "sleep" + + # kill thread, ensure it dies in a reasonable amount of time + t.kill + a = 0 + while t.alive? and a < 5000 + sleep 0.001 + a += 1 + end + a.should < 5000 + end + + it "can be interrupted by Thread#raise" do + t = Thread.new { + -> { + @server.accept + }.should raise_error(Exception, "interrupted") + } + + Thread.pass while t.status and t.status != "sleep" + t.raise Exception, "interrupted" + t.join + end + + it "is automatically retried when interrupted by SIGVTALRM" do + t = Thread.new do + client = @server.accept + value = client.read(2) + client.close + value + end + + Thread.pass while t.status and t.status != "sleep" + # Thread#backtrace uses SIGVTALRM on TruffleRuby and potentially other implementations. + # Sending a signal to a thread is not possible with Ruby APIs. + t.backtrace.join("\n").should =~ /in [`'](?:TCPServer#)?accept'/ + + socket = TCPSocket.new('127.0.0.1', @port) + socket.write("OK") + socket.close + + t.value.should == "OK" + end + + it "raises an IOError if the socket is closed" do + @server.close + -> { @server.accept }.should raise_error(IOError) + end +end + +describe 'TCPServer#accept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + describe 'without a connected client' do + it 'blocks the caller' do + -> { @server.accept }.should block_caller + end + end + + describe 'with a connected client' do + before do + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + @socket.close if @socket + @client.close + end + + it 'returns a TCPSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(TCPSocket) + end + + platform_is_not :windows do + it "returns a TCPSocket which is set to nonblocking" do + require 'io/nonblock' + @socket = @server.accept + @socket.should.nonblock? + end + end + + it "returns a TCPSocket which is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? + end + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/gets_spec.rb b/spec/ruby/library/socket/tcpserver/gets_spec.rb new file mode 100644 index 0000000000..417976d737 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/gets_spec.rb @@ -0,0 +1,16 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer#gets" do + before :each do + @server = TCPServer.new(SocketSpecs.hostname, 0) + end + + after :each do + @server.close + end + + it "raises Errno::ENOTCONN on gets" do + -> { @server.gets }.should raise_error(Errno::ENOTCONN) + end +end diff --git a/spec/ruby/library/socket/tcpserver/initialize_spec.rb b/spec/ruby/library/socket/tcpserver/initialize_spec.rb new file mode 100644 index 0000000000..4ddd1f465f --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/initialize_spec.rb @@ -0,0 +1,101 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPServer#initialize' do + describe 'with a single Integer argument' do + before do + @server = TCPServer.new(0) + end + + after do + @server.close + end + + it 'sets the port to the given argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + platform_is_not :windows do + it 'sets the hostname to 0.0.0.0 or ::' do + a = @server.local_address + a.ip_address.should == (a.ipv6? ? '::' : '0.0.0.0') + end + end + + it "sets the socket to binmode" do + @server.binmode?.should be_true + end + end + + describe 'with a single String argument containing a numeric value' do + before do + @server = TCPServer.new('0') + end + + after do + @server.close + end + + it 'sets the port to the given argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + platform_is_not :windows do + it 'sets the hostname to 0.0.0.0 or ::' do + a = @server.local_address + a.ip_address.should == (a.ipv6? ? '::' : '0.0.0.0') + end + end + end + + describe 'with a single String argument containing a non numeric value' do + it 'raises SocketError' do + -> { TCPServer.new('cats') }.should raise_error(SocketError) + end + end + + describe 'with a String and an Integer' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + it 'sets the port to the given port argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + it 'sets the hostname to the given host argument' do + @server.local_address.ip_address.should == ip_address + end + end + end + + describe 'with a String and a custom object' do + before do + dummy = mock(:dummy) + dummy.stub!(:to_str).and_return('0') + + @server = TCPServer.new('127.0.0.1', dummy) + end + + after do + @server.close + end + + it 'sets the port to the given port argument' do + @server.local_address.ip_port.should be_kind_of(Integer) + @server.local_address.ip_port.should > 0 + end + + it 'sets the hostname to the given host argument' do + @server.local_address.ip_address.should == '127.0.0.1' + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/listen_spec.rb b/spec/ruby/library/socket/tcpserver/listen_spec.rb new file mode 100644 index 0000000000..c877fdced6 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/listen_spec.rb @@ -0,0 +1,22 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPServer#listen' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + it 'returns 0' do + @server.listen(1).should == 0 + end + + it "raises when the given argument can't be coerced to an Integer" do + -> { @server.listen('cats') }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/new_spec.rb b/spec/ruby/library/socket/tcpserver/new_spec.rb new file mode 100644 index 0000000000..dd1ba676bd --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/new_spec.rb @@ -0,0 +1,137 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer.new" do + after :each do + @server.close if @server && !@server.closed? + end + + it "binds to a host and a port" do + @server = TCPServer.new('127.0.0.1', 0) + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + # on some platforms (Mac), MRI + # returns comma at the end. + addr[2].should =~ /^#{SocketSpecs.hostname}\b/ + addr[3].should == '127.0.0.1' + end + + it "binds to localhost and a port with either IPv4 or IPv6" do + @server = TCPServer.new(SocketSpecs.hostname, 0) + addr = @server.addr + addr[1].should be_kind_of(Integer) + if addr[0] == 'AF_INET' + addr[2].should =~ /^#{SocketSpecs.hostname}\b/ + addr[3].should == '127.0.0.1' + else + addr[2].should =~ /^#{SocketSpecs.hostname('::1')}\b/ + addr[3].should == '::1' + end + end + + it "binds to INADDR_ANY if the hostname is empty" do + @server = TCPServer.new('', 0) + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "binds to INADDR_ANY if the hostname is empty and the port is a string" do + @server = TCPServer.new('', '0') + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "binds to a port if the port is explicitly nil" do + @server = TCPServer.new('', nil) + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "binds to a port if the port is an empty string" do + @server = TCPServer.new('', '') + addr = @server.addr + addr[0].should == 'AF_INET' + addr[1].should be_kind_of(Integer) + addr[2].should == '0.0.0.0' + addr[3].should == '0.0.0.0' + end + + it "coerces port to string, then determines port from that number or service name" do + -> { TCPServer.new(SocketSpecs.hostname, Object.new) }.should raise_error(TypeError) + + port = Object.new + port.should_receive(:to_str).and_return("0") + + @server = TCPServer.new(SocketSpecs.hostname, port) + addr = @server.addr + addr[1].should be_kind_of(Integer) + + # TODO: This should also accept strings like 'https', but I don't know how to + # pick such a service port that will be able to reliably bind... + end + + it "has a single argument form and treats it as a port number" do + @server = TCPServer.new(0) + addr = @server.addr + addr[1].should be_kind_of(Integer) + end + + it "coerces port to a string when it is the only argument" do + -> { TCPServer.new(Object.new) }.should raise_error(TypeError) + + port = Object.new + port.should_receive(:to_str).and_return("0") + + @server = TCPServer.new(port) + addr = @server.addr + addr[1].should be_kind_of(Integer) + end + + it "does not use the given block and warns to use TCPServer::open" do + -> { + @server = TCPServer.new(0) { raise } + }.should complain(/warning: TCPServer::new\(\) does not take block; use TCPServer::open\(\) instead/) + end + + it "raises Errno::EADDRNOTAVAIL when the address is unknown" do + -> { TCPServer.new("1.2.3.4", 0) }.should raise_error(Errno::EADDRNOTAVAIL) + end + + # There is no way to make this fail-proof on all machines, because + # DNS servers like opendns return A records for ANY host, including + # traditionally invalidly named ones. + quarantine! do + it "raises a SocketError when the host is unknown" do + -> { + TCPServer.new("--notavalidname", 0) + }.should raise_error(SocketError) + end + end + + it "raises Errno::EADDRINUSE when address is already in use" do + @server = TCPServer.new('127.0.0.1', 0) + -> { + @server = TCPServer.new('127.0.0.1', @server.addr[1]) + }.should raise_error(Errno::EADDRINUSE) + end + + platform_is_not :windows, :aix do + # A known bug in AIX. getsockopt(2) does not properly set + # the fifth argument for SO_REUSEADDR. + it "sets SO_REUSEADDR on the resulting server" do + @server = TCPServer.new('127.0.0.1', 0) + @server.getsockopt(:SOCKET, :REUSEADDR).data.should_not == "\x00\x00\x00\x00" + @server.getsockopt(:SOCKET, :REUSEADDR).int.should_not == 0 + end + end +end diff --git a/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb b/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb new file mode 100644 index 0000000000..bd7d33faf4 --- /dev/null +++ b/spec/ruby/library/socket/tcpserver/sysaccept_spec.rb @@ -0,0 +1,66 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPServer#sysaccept" do + before :each do + @server = TCPServer.new(SocketSpecs.hostname, 0) + @port = @server.addr[1] + end + + after :each do + @server.close unless @server.closed? + end + + it 'blocks if no connections' do + -> { @server.sysaccept }.should block_caller + end + + it 'returns file descriptor of an accepted connection' do + begin + sock = TCPSocket.new(SocketSpecs.hostname, @port) + + fd = @server.sysaccept + + fd.should be_kind_of(Integer) + ensure + sock.close if sock && !sock.closed? + IO.for_fd(fd).close if fd + end + end +end + +describe 'TCPServer#sysaccept' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + end + + after do + @server.close + end + + describe 'without a connected client' do + it 'blocks the caller' do + -> { @server.sysaccept }.should block_caller + end + end + + describe 'with a connected client' do + before do + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + Socket.for_fd(@fd).close if @fd + @client.close + end + + it 'returns a new file descriptor as an Integer' do + @fd = @server.sysaccept + + @fd.should be_kind_of(Integer) + @fd.should_not == @client.fileno + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb new file mode 100644 index 0000000000..5a2c704f35 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb @@ -0,0 +1,119 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +# TODO: verify these for windows +describe "TCPSocket.gethostbyname" do + before :each do + suppress_warning do + @host_info = TCPSocket.gethostbyname(SocketSpecs.hostname) + end + end + + it "returns an array elements of information on the hostname" do + @host_info.should be_kind_of(Array) + end + + platform_is_not :windows do + it "returns the canonical name as first value" do + @host_info[0].should == SocketSpecs.hostname + end + + it "returns the address type as the third value" do + address_type = @host_info[2] + [Socket::AF_INET, Socket::AF_INET6].include?(address_type).should be_true + end + + it "returns the IP address as the fourth value" do + ip = @host_info[3] + ["127.0.0.1", "::1"].include?(ip).should be_true + end + end + + platform_is :windows do + quarantine! do # name lookup seems not working on Windows CI + it "returns the canonical name as first value" do + host = "#{ENV['COMPUTERNAME'].downcase}" + host << ".#{ENV['USERDNSDOMAIN'].downcase}" if ENV['USERDNSDOMAIN'] + @host_info[0].should == host + end + end + + it "returns the address type as the third value" do + @host_info[2].should == Socket::AF_INET + end + + it "returns the IP address as the fourth value" do + @host_info[3].should == "127.0.0.1" + end + end + + it "returns any aliases to the address as second value" do + @host_info[1].should be_kind_of(Array) + end +end + +describe 'TCPSocket.gethostbyname' do + it 'returns an Array' do + suppress_warning do + TCPSocket.gethostbyname('127.0.0.1').should be_an_instance_of(Array) + end + end + + describe 'using a hostname' do + describe 'the returned Array' do + before do + suppress_warning do + @array = TCPSocket.gethostbyname('127.0.0.1') + end + end + + it 'includes the canonical name as the 1st value' do + @array[0].should == '127.0.0.1' + end + + it 'includes an array of alternative hostnames as the 2nd value' do + @array[1].should be_an_instance_of(Array) + end + + it 'includes the address family as the 3rd value' do + @array[2].should be_kind_of(Integer) + end + + it 'includes the IP addresses as all the remaining values' do + ips = %w{::1 127.0.0.1} + + ips.include?(@array[3]).should == true + + # Not all machines might have both IPv4 and IPv6 set up, so this value is + # optional. + ips.include?(@array[4]).should == true if @array[4] + end + end + end + + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'the returned Array' do + before do + suppress_warning do + @array = TCPSocket.gethostbyname(ip_address) + end + end + + it 'includes the IP address as the 1st value' do + @array[0].should == ip_address + end + + it 'includes an empty list of aliases as the 2nd value' do + @array[1].should == [] + end + + it 'includes the address family as the 3rd value' do + @array[2].should == family + end + + it 'includes the IP address as the 4th value' do + @array[3].should == ip_address + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb new file mode 100644 index 0000000000..d7feb9751b --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb @@ -0,0 +1,100 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/new' + +describe 'TCPSocket#initialize' do + it_behaves_like :tcpsocket_new, :new + + describe "with a running server" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "does not use the given block and warns to use TCPSocket::open" do + -> { + @socket = TCPSocket.new(@hostname, @server.port, nil) { raise } + }.should complain(/warning: TCPSocket::new\(\) does not take block; use TCPSocket::open\(\) instead/) + end + end +end + +describe 'TCPSocket#initialize' do + SocketSpecs.each_ip_protocol do |family, ip_address| + describe 'when no server is listening on the given address' do + it 'raises Errno::ECONNREFUSED' do + -> { TCPSocket.new(ip_address, 666) }.should raise_error(Errno::ECONNREFUSED) + end + end + + describe 'when a server is listening on the given address' do + before do + @server = TCPServer.new(ip_address, 0) + @port = @server.connect_address.ip_port + end + + after do + @client.close if @client + @server.close + end + + it 'returns a TCPSocket when using an Integer as the port' do + @client = TCPSocket.new(ip_address, @port) + @client.should be_an_instance_of(TCPSocket) + end + + it 'returns a TCPSocket when using a String as the port' do + @client = TCPSocket.new(ip_address, @port.to_s) + @client.should be_an_instance_of(TCPSocket) + end + + it 'raises SocketError when the port number is a non numeric String' do + -> { TCPSocket.new(ip_address, 'cats') }.should raise_error(SocketError) + end + + it 'set the socket to binmode' do + @client = TCPSocket.new(ip_address, @port) + @client.binmode?.should be_true + end + + it 'connects to the right address' do + @client = TCPSocket.new(ip_address, @port) + + @client.remote_address.ip_address.should == @server.local_address.ip_address + @client.remote_address.ip_port.should == @server.local_address.ip_port + end + + platform_is_not :windows do + it "creates a socket which is set to nonblocking" do + require 'io/nonblock' + @client = TCPSocket.new(ip_address, @port) + @client.should.nonblock? + end + end + + it "creates a socket which is set to close on exec" do + @client = TCPSocket.new(ip_address, @port) + @client.should.close_on_exec? + end + + describe 'using a local address and service' do + it 'binds the client socket to the local address and service' do + @client = TCPSocket.new(ip_address, @port, ip_address, 0) + + @client.local_address.ip_address.should == ip_address + + @client.local_address.ip_port.should > 0 + @client.local_address.ip_port.should_not == @port + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/local_address_spec.rb b/spec/ruby/library/socket/tcpsocket/local_address_spec.rb new file mode 100644 index 0000000000..ce66d5ff8f --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/local_address_spec.rb @@ -0,0 +1,73 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPSocket#local_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = TCPSocket.new(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @sock.local_address.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @sock.local_address.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + + it 'uses a randomly assigned local port' do + @sock.local_address.ip_port.should > 0 + @sock.local_address.ip_port.should_not == @port + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = TCPSocket.new(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/open_spec.rb b/spec/ruby/library/socket/tcpsocket/open_spec.rb new file mode 100644 index 0000000000..0c0b579064 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/open_spec.rb @@ -0,0 +1,6 @@ +require_relative "../../../spec_helper" +require_relative 'shared/new' + +describe "TCPSocket.open" do + it_behaves_like :tcpsocket_new, :open +end diff --git a/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb b/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb new file mode 100644 index 0000000000..d365ecd335 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' + +describe "TCPSocket partial closability" do + + before :each do + @server = TCPServer.new("127.0.0.1", 0) + @s1 = TCPSocket.new("127.0.0.1", @server.addr[1]) + @s2 = @server.accept + end + + after :each do + @server.close + @s1.close + @s2.close + end + + it_should_behave_like :partially_closable_sockets + +end diff --git a/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb new file mode 100644 index 0000000000..6ce5a41b58 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb @@ -0,0 +1,48 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPSocket#recv_nonblock" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "returns a String read from the socket" do + @socket = TCPSocket.new @hostname, @server.port + @socket.write "TCPSocket#recv_nonblock" + + # Wait for the server to echo. This spec is testing the return + # value, not the non-blocking behavior. + # + # TODO: Figure out a good way to test non-blocking. + IO.select([@socket]) + @socket.recv_nonblock(50).should == "TCPSocket#recv_nonblock" + end + + it 'writes the read to a buffer from the socket' do + @socket = TCPSocket.new @hostname, @server.port + @socket.write "TCPSocket#recv_nonblock" + + # Wait for the server to echo. This spec is testing the return + # value, not the non-blocking behavior. + # + # TODO: Figure out a good way to test non-blocking. + IO.select([@socket]) + buffer = "".b + @socket.recv_nonblock(50, 0, buffer) + buffer.should == 'TCPSocket#recv_nonblock' + end + + it 'returns :wait_readable in exceptionless mode' do + @socket = TCPSocket.new @hostname, @server.port + @socket.recv_nonblock(50, exception: false).should == :wait_readable + end +end diff --git a/spec/ruby/library/socket/tcpsocket/recv_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_spec.rb new file mode 100644 index 0000000000..f380db670d --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/recv_spec.rb @@ -0,0 +1,28 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPSocket#recv' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @client = TCPSocket.new(ip_address, @server.connect_address.ip_port) + end + + after do + @client.close + @server.close + end + + it 'returns the message data' do + @client.write('hello') + + socket = @server.accept + + begin + socket.recv(5).should == 'hello' + ensure + socket.close + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb b/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb new file mode 100644 index 0000000000..eb9dabc075 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/remote_address_spec.rb @@ -0,0 +1,72 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'TCPSocket#remote_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = TCPServer.new(ip_address, 0) + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = TCPSocket.new(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_INET as the address family' do + @sock.remote_address.afamily.should == family + end + + it 'uses PF_INET as the protocol family' do + @sock.remote_address.pfamily.should == family + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.remote_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + + it 'uses the correct port' do + @sock.remote_address.ip_port.should == @port + end + + it 'uses 0 as the protocol' do + @sock.remote_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = TCPSocket.new(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb b/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb new file mode 100644 index 0000000000..8b728b7522 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb @@ -0,0 +1,45 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "TCPSocket#setsockopt" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + @sock = TCPSocket.new @hostname, @server.port + end + + after :each do + @sock.close unless @sock.closed? + @server.shutdown + end + + describe "using constants" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1).should == 0 + end + end + + describe "using symbols" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1).should == 0 + end + + context "without prefix" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt(:TCP, :NODELAY, 1).should == 0 + end + end + end + + describe "using strings" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt('IPPROTO_TCP', 'TCP_NODELAY', 1).should == 0 + end + + context "without prefix" do + it "sets the TCP nodelay to 1" do + @sock.setsockopt('TCP', 'NODELAY', 1).should == 0 + end + end + end +end diff --git a/spec/ruby/library/socket/tcpsocket/shared/new.rb b/spec/ruby/library/socket/tcpsocket/shared/new.rb new file mode 100644 index 0000000000..0e405253c8 --- /dev/null +++ b/spec/ruby/library/socket/tcpsocket/shared/new.rb @@ -0,0 +1,102 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :tcpsocket_new, shared: true do + it "requires a hostname and a port as arguments" do + -> { TCPSocket.send(@method) }.should raise_error(ArgumentError) + end + + it "refuses the connection when there is no server to connect to" do + -> do + TCPSocket.send(@method, SocketSpecs.hostname, SocketSpecs.reserved_unused_port) + end.should raise_error(SystemCallError) {|e| + [Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL].should include(e.class) + } + end + + it 'raises IO::TimeoutError with :connect_timeout when no server is listening on the given address' do + -> { + TCPSocket.send(@method, "192.0.2.1", 80, connect_timeout: 0) + }.should raise_error(IO::TimeoutError) + rescue Errno::ENETUNREACH + # In the case all network interfaces down. + # raise_error cannot deal with multiple expected exceptions + end + + describe "with a running server" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "silently ignores 'nil' as the third parameter" do + @socket = TCPSocket.send(@method, @hostname, @server.port, nil) + @socket.should be_an_instance_of(TCPSocket) + end + + it "connects to a listening server with host and port" do + @socket = TCPSocket.send(@method, @hostname, @server.port) + @socket.should be_an_instance_of(TCPSocket) + end + + it "connects to a server when passed local_host argument" do + @socket = TCPSocket.send(@method, @hostname, @server.port, @hostname) + @socket.should be_an_instance_of(TCPSocket) + end + + it "connects to a server when passed local_host and local_port arguments" do + retries = 0 + max_retries = 3 + + begin + retries += 1 + server = TCPServer.new(SocketSpecs.hostname, 0) + begin + available_port = server.addr[1] + ensure + server.close + end + @socket = TCPSocket.send(@method, @hostname, @server.port, + @hostname, available_port) + rescue Errno::EADDRINUSE + raise if retries >= max_retries + retry + end + @socket.should be_an_instance_of(TCPSocket) + end + + it "has an address once it has connected to a listening server" do + @socket = TCPSocket.send(@method, @hostname, @server.port) + @socket.should be_an_instance_of(TCPSocket) + + # TODO: Figure out how to abstract this. You can get AF_INET + # from 'Socket.getaddrinfo(hostname, nil)[0][3]' but socket.addr + # will return AF_INET6. At least this check will weed out clearly + # erroneous values. + @socket.addr[0].should =~ /^AF_INET6?/ + + case @socket.addr[0] + when 'AF_INET' + @socket.addr[3].should == SocketSpecs.addr(:ipv4) + when 'AF_INET6' + @socket.addr[3].should == SocketSpecs.addr(:ipv6) + end + + @socket.addr[1].should be_kind_of(Integer) + @socket.addr[2].should =~ /^#{@hostname}/ + end + + it "connects to a server when passed connect_timeout argument" do + @socket = TCPSocket.send(@method, @hostname, @server.port, connect_timeout: 1) + @socket.should be_an_instance_of(TCPSocket) + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/bind_spec.rb b/spec/ruby/library/socket/udpsocket/bind_spec.rb new file mode 100644 index 0000000000..08b386e941 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/bind_spec.rb @@ -0,0 +1,83 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket#bind" do + before :each do + @socket = UDPSocket.new + end + + after :each do + @socket.close unless @socket.closed? + end + + it "binds the socket to a port" do + @socket.bind(SocketSpecs.hostname, 0) + @socket.addr[1].should be_kind_of(Integer) + end + + it "raises Errno::EINVAL when already bound" do + @socket.bind(SocketSpecs.hostname, 0) + + -> { + @socket.bind(SocketSpecs.hostname, @socket.addr[1]) + }.should raise_error(Errno::EINVAL) + end + + it "receives a hostname and a port" do + @socket.bind(SocketSpecs.hostname, 0) + + port, host = Socket.unpack_sockaddr_in(@socket.getsockname) + + host.should == "127.0.0.1" + port.should == @socket.addr[1] + end + + it "binds to INADDR_ANY if the hostname is empty" do + @socket.bind("", 0).should == 0 + port, host = Socket.unpack_sockaddr_in(@socket.getsockname) + host.should == "0.0.0.0" + port.should == @socket.addr[1] + end +end + +describe 'UDPSocket#bind' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @socket = UDPSocket.new(family) + end + + after do + @socket.close + end + + it 'binds to an address and port' do + @socket.bind(ip_address, 0).should == 0 + + @socket.local_address.ip_address.should == ip_address + @socket.local_address.ip_port.should > 0 + end + + it 'binds to an address and port using String arguments' do + @socket.bind(ip_address, '0').should == 0 + + @socket.local_address.ip_address.should == ip_address + @socket.local_address.ip_port.should > 0 + end + + it 'can receive data after being bound to an address' do + @socket.bind(ip_address, 0) + + addr = @socket.connect_address + client = UDPSocket.new(family) + + client.connect(addr.ip_address, addr.ip_port) + client.write('hello') + + begin + @socket.recv(6).should == 'hello' + ensure + client.close + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/connect_spec.rb b/spec/ruby/library/socket/udpsocket/connect_spec.rb new file mode 100644 index 0000000000..d92bdeb981 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/connect_spec.rb @@ -0,0 +1,35 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#connect' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @socket = UDPSocket.new(family) + end + + after do + @socket.close + end + + it 'connects to an address even when it is not used' do + @socket.connect(ip_address, 9996).should == 0 + end + + it 'can send data after connecting' do + receiver = UDPSocket.new(family) + + receiver.bind(ip_address, 0) + + addr = receiver.connect_address + + @socket.connect(addr.ip_address, addr.ip_port) + @socket.write('hello') + + begin + receiver.recv(6).should == 'hello' + ensure + receiver.close + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/initialize_spec.rb b/spec/ruby/library/socket/udpsocket/initialize_spec.rb new file mode 100644 index 0000000000..ecf0043c10 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/initialize_spec.rb @@ -0,0 +1,53 @@ +require_relative '../spec_helper' + +describe 'UDPSocket#initialize' do + after do + @socket.close if @socket + end + + it 'initializes a new UDPSocket' do + @socket = UDPSocket.new + @socket.should be_an_instance_of(UDPSocket) + end + + it 'initializes a new UDPSocket using an Integer' do + @socket = UDPSocket.new(Socket::AF_INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'initializes a new UDPSocket using a Symbol' do + @socket = UDPSocket.new(:INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'initializes a new UDPSocket using a String' do + @socket = UDPSocket.new('INET') + @socket.should be_an_instance_of(UDPSocket) + end + + it 'sets the socket to binmode' do + @socket = UDPSocket.new(:INET) + @socket.binmode?.should be_true + end + + platform_is_not :windows do + it 'sets the socket to nonblock' do + require 'io/nonblock' + @socket = UDPSocket.new(:INET) + @socket.should.nonblock? + end + end + + it 'sets the socket to close on exec' do + @socket = UDPSocket.new(:INET) + @socket.should.close_on_exec? + end + + it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT when given an invalid address family' do + -> { + UDPSocket.new(666) + }.should raise_error(SystemCallError) { |e| + [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class) + } + end +end diff --git a/spec/ruby/library/socket/udpsocket/inspect_spec.rb b/spec/ruby/library/socket/udpsocket/inspect_spec.rb new file mode 100644 index 0000000000..e212120b14 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/inspect_spec.rb @@ -0,0 +1,17 @@ +require_relative '../spec_helper' + +describe 'UDPSocket#inspect' do + before do + @socket = UDPSocket.new + @socket.bind('127.0.0.1', 0) + end + + after do + @socket.close + end + + it 'returns a String with the fd, family, address and port' do + port = @socket.addr[1] + @socket.inspect.should == "#<UDPSocket:fd #{@socket.fileno}, AF_INET, 127.0.0.1, #{port}>" + end +end diff --git a/spec/ruby/library/socket/udpsocket/local_address_spec.rb b/spec/ruby/library/socket/udpsocket/local_address_spec.rb new file mode 100644 index 0000000000..92e4cc10c7 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/local_address_spec.rb @@ -0,0 +1,80 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#local_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM, Socket::IPPROTO_UDP) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses the correct address family' do + @sock.local_address.afamily.should == family + end + + it 'uses the correct protocol family' do + @sock.local_address.pfamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + + it 'uses a randomly assigned local port' do + @sock.local_address.ip_port.should > 0 + @sock.local_address.ip_port.should_not == @port + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.local_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/new_spec.rb b/spec/ruby/library/socket/udpsocket/new_spec.rb new file mode 100644 index 0000000000..79bfcb624d --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/new_spec.rb @@ -0,0 +1,40 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket.new' do + after :each do + @socket.close if @socket && !@socket.closed? + end + + it 'without arguments' do + @socket = UDPSocket.new + @socket.should be_an_instance_of(UDPSocket) + end + + it 'using Integer argument' do + @socket = UDPSocket.new(Socket::AF_INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'using Symbol argument' do + @socket = UDPSocket.new(:INET) + @socket.should be_an_instance_of(UDPSocket) + end + + it 'using String argument' do + @socket = UDPSocket.new('INET') + @socket.should be_an_instance_of(UDPSocket) + end + + it "does not use the given block and warns to use UDPSocket::open" do + -> { + @socket = UDPSocket.new { raise } + }.should complain(/warning: UDPSocket::new\(\) does not take block; use UDPSocket::open\(\) instead/) + end + + it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT if unsupported family passed' do + -> { UDPSocket.new(-1) }.should raise_error(SystemCallError) { |e| + [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class) + } + end +end diff --git a/spec/ruby/library/socket/udpsocket/open_spec.rb b/spec/ruby/library/socket/udpsocket/open_spec.rb new file mode 100644 index 0000000000..e4dbb2ee2a --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/open_spec.rb @@ -0,0 +1,13 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket.open" do + after :each do + @socket.close if @socket && !@socket.closed? + end + + it "allows calls to open without arguments" do + @socket = UDPSocket.open + @socket.should be_kind_of(UDPSocket) + end +end diff --git a/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb new file mode 100644 index 0000000000..b804099589 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb @@ -0,0 +1,111 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#recvfrom_nonblock' do + SocketSpecs.each_ip_protocol do |family, ip_address, family_name| + before do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + describe 'using an unbound socket' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + end + end + + describe 'using a bound socket' do + before do + @server.bind(ip_address, 0) + + addr = @server.connect_address + + @client.connect(addr.ip_address, addr.ip_port) + end + + describe 'without any data available' do + it 'raises IO::WaitReadable' do + -> { @server.recvfrom_nonblock(1) }.should raise_error(IO::WaitReadable) + end + + it 'returns :wait_readable with exception: false' do + @server.recvfrom_nonblock(1, exception: false).should == :wait_readable + end + end + + platform_is_not :windows do + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns an Array containing the data and an Array' do + IO.select([@server]) + @server.recvfrom_nonblock(1).should be_an_instance_of(Array) + end + + it 'writes the data to the buffer when one is present' do + buffer = "".b + IO.select([@server]) + @server.recvfrom_nonblock(1, 0, buffer) + buffer.should == 'h' + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + IO.select([@server]) + message, = @server.recvfrom_nonblock(1, 0, buffer) + + message.should.equal?(buffer) + buffer.encoding.should == Encoding::ISO_8859_1 + end + + describe 'the returned Array' do + before do + IO.select([@server]) + @array = @server.recvfrom_nonblock(1) + end + + it 'contains the data at index 0' do + @array[0].should == 'h' + end + + it 'contains an Array at index 1' do + @array[1].should be_an_instance_of(Array) + end + end + + describe 'the returned address Array' do + before do + IO.select([@server]) + @addr = @server.recvfrom_nonblock(1)[1] + end + + it 'uses the correct address family' do + @addr[0].should == family_name + end + + it 'uses the port of the client' do + @addr[1].should == @client.local_address.ip_port + end + + it 'uses the hostname of the client' do + @addr[2].should == ip_address + end + + it 'uses the IP address of the client' do + @addr[3].should == ip_address + end + end + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/remote_address_spec.rb b/spec/ruby/library/socket/udpsocket/remote_address_spec.rb new file mode 100644 index 0000000000..94889ce560 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/remote_address_spec.rb @@ -0,0 +1,79 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UDPSocket#remote_address' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = Socket.new(family, :DGRAM, Socket::IPPROTO_UDP) + + @server.bind(Socket.sockaddr_in(0, ip_address)) + + @host = @server.connect_address.ip_address + @port = @server.connect_address.ip_port + end + + after do + @server.close + end + + describe 'using an explicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(@host, @port) + end + + after do + @sock.close + end + + it 'returns an Addrinfo' do + @sock.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses the correct address family' do + @sock.remote_address.afamily.should == family + end + + it 'uses the correct protocol family' do + @sock.remote_address.pfamily.should == family + end + + it 'uses SOCK_DGRAM as the socket type' do + @sock.remote_address.socktype.should == Socket::SOCK_DGRAM + end + + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + + it 'uses the correct port' do + @sock.remote_address.ip_port.should == @port + end + + it 'uses 0 as the protocol' do + @sock.remote_address.protocol.should == 0 + end + end + end + + describe 'using an implicit hostname' do + before do + @sock = UDPSocket.new(family) + + @sock.connect(nil, @port) + end + + after do + @sock.close + end + + describe 'the returned Addrinfo' do + it 'uses the correct IP address' do + @sock.remote_address.ip_address.should == @host + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/send_spec.rb b/spec/ruby/library/socket/udpsocket/send_spec.rb new file mode 100644 index 0000000000..6dd5f67bea --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/send_spec.rb @@ -0,0 +1,154 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket#send" do + before :each do + @port = nil + @server_thread = Thread.new do + @server = UDPSocket.open + begin + @server.bind(nil, 0) + @port = @server.addr[1] + begin + @msg = @server.recvfrom_nonblock(64) + rescue IO::WaitReadable + IO.select([@server]) + retry + end + ensure + @server.close if !@server.closed? + end + end + Thread.pass while @server_thread.status and !@port + end + + after :each do + @server_thread.join + end + + it "sends data in ad hoc mode" do + @socket = UDPSocket.open + @socket.send("ad hoc", 0, SocketSpecs.hostname, @port) + @socket.close + @server_thread.join + + @msg[0].should == "ad hoc" + @msg[1][0].should == "AF_INET" + @msg[1][1].should be_kind_of(Integer) + @msg[1][3].should == "127.0.0.1" + end + + it "sends data in ad hoc mode (with port given as a String)" do + @socket = UDPSocket.open + @socket.send("ad hoc", 0, SocketSpecs.hostname, @port.to_s) + @socket.close + @server_thread.join + + @msg[0].should == "ad hoc" + @msg[1][0].should == "AF_INET" + @msg[1][1].should be_kind_of(Integer) + @msg[1][3].should == "127.0.0.1" + end + + it "sends data in connection mode" do + @socket = UDPSocket.open + @socket.connect(SocketSpecs.hostname, @port) + @socket.send("connection-based", 0) + @socket.close + @server_thread.join + + @msg[0].should == "connection-based" + @msg[1][0].should == "AF_INET" + @msg[1][1].should be_kind_of(Integer) + @msg[1][3].should == "127.0.0.1" + end + + it "raises EMSGSIZE if data is too big" do + @socket = UDPSocket.open + begin + -> do + @socket.send('1' * 100_000, 0, SocketSpecs.hostname, @port.to_s) + end.should raise_error(Errno::EMSGSIZE) + ensure + @socket.send("ad hoc", 0, SocketSpecs.hostname, @port) + @socket.close + @server_thread.join + end + end +end + +describe 'UDPSocket#send' do + SocketSpecs.each_ip_protocol do |family, ip_address| + before do + @server = UDPSocket.new(family) + @client = UDPSocket.new(family) + + @server.bind(ip_address, 0) + + @addr = @server.connect_address + end + + after do + @server.close + @client.close + end + + describe 'using a disconnected socket' do + describe 'without a destination address' do + it "raises #{SocketSpecs.dest_addr_req_error}" do + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as separate arguments' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @addr.ip_address, @addr.ip_port).should == 5 + end + + it 'does not persist the connection after sending data' do + @client.send('hello', 0, @addr.ip_address, @addr.ip_port) + + -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) + end + end + + describe 'with a destination address as a single String argument' do + it 'returns the amount of sent bytes' do + @client.send('hello', 0, @server.getsockname).should == 5 + end + end + end + + describe 'using a connected socket' do + describe 'without an explicit destination address' do + before do + @client.connect(@addr.ip_address, @addr.ip_port) + end + + it 'returns the amount of bytes written' do + @client.send('hello', 0).should == 5 + end + end + + describe 'with an explicit destination address' do + before do + @alt_server = UDPSocket.new(family) + + @alt_server.bind(ip_address, 0) + end + + after do + @alt_server.close + end + + it 'sends the data to the given address instead' do + @client.send('hello', 0, @alt_server.getsockname).should == 5 + + -> { @server.recv(5) }.should block_caller + + @alt_server.recv(5).should == 'hello' + end + end + end + end +end diff --git a/spec/ruby/library/socket/udpsocket/write_spec.rb b/spec/ruby/library/socket/udpsocket/write_spec.rb new file mode 100644 index 0000000000..c971f29b62 --- /dev/null +++ b/spec/ruby/library/socket/udpsocket/write_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UDPSocket#write" do + it "raises EMSGSIZE if msg is too long" do + begin + host = SocketSpecs.hostname + s1 = UDPSocket.new + s1.bind(host, 0) + s2 = UDPSocket.new + s2.connect(host, s1.addr[1]) + + -> do + s2.write('1' * 100_000) + end.should raise_error(Errno::EMSGSIZE) + ensure + s1.close if s1 && !s1.closed? + s2.close if s2 && !s2.closed? + end + end +end diff --git a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb new file mode 100644 index 0000000000..f67941b296 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb @@ -0,0 +1,87 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXServer#accept_nonblock" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + + @socket = @server.accept_nonblock + @client.send("foobar", 0) + end + + after :each do + @socket.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "accepts a connection in a non-blocking way" do + data = @socket.recvfrom(6).first + data.should == "foobar" + end + + it "returns a UNIXSocket" do + @socket.should be_kind_of(UNIXSocket) + end + + it 'returns :wait_readable in exceptionless mode' do + @server.accept_nonblock(exception: false).should == :wait_readable + end +end + +describe 'UNIXServer#accept_nonblock' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r(@path) + end + + describe 'without a client' do + it 'raises IO::WaitReadable' do + -> { @server.accept_nonblock }.should raise_error(IO::WaitReadable) + end + end + + describe 'with a client' do + before do + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @socket.close if @socket + end + + describe 'without any data' do + it 'returns a UNIXSocket' do + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(UNIXSocket) + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns a UNIXSocket' do + @socket = @server.accept_nonblock + @socket.should be_an_instance_of(UNIXSocket) + end + + describe 'the returned UNIXSocket' do + it 'can read the data written' do + @socket = @server.accept_nonblock + @socket.recv(5).should == 'hello' + end + end + end + end +end diff --git a/spec/ruby/library/socket/unixserver/accept_spec.rb b/spec/ruby/library/socket/unixserver/accept_spec.rb new file mode 100644 index 0000000000..cc2c922b6f --- /dev/null +++ b/spec/ruby/library/socket/unixserver/accept_spec.rb @@ -0,0 +1,126 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXServer#accept" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end + + after :each do + @server.close if @server + SocketSpecs.rm_socket @path + end + + it "accepts what is written by the client" do + client = UNIXSocket.open(@path) + + client.send('hello', 0) + + sock = @server.accept + begin + data, info = sock.recvfrom(5) + + data.should == 'hello' + info.should_not be_empty + ensure + sock.close + client.close + end + end + + it "can be interrupted by Thread#kill" do + t = Thread.new { + @server.accept + } + Thread.pass while t.status and t.status != "sleep" + + # kill thread, ensure it dies in a reasonable amount of time + t.kill + a = 0 + while t.alive? and a < 5000 + sleep 0.001 + a += 1 + end + a.should < 5000 + end + + it "can be interrupted by Thread#raise" do + t = Thread.new { + -> { + @server.accept + }.should raise_error(Exception, "interrupted") + } + + Thread.pass while t.status and t.status != "sleep" + t.raise Exception, "interrupted" + t.join + end +end + +describe 'UNIXServer#accept' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + rm_r(@path) + end + + describe 'without a client' do + it 'blocks the calling thread' do + -> { @server.accept }.should block_caller + end + end + + describe 'with a client' do + before do + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @socket.close if @socket + end + + describe 'without any data' do + it 'returns a UNIXSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(UNIXSocket) + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns a UNIXSocket' do + @socket = @server.accept + @socket.should be_an_instance_of(UNIXSocket) + end + + describe 'the returned UNIXSocket' do + it 'can read the data written' do + @socket = @server.accept + @socket.recv(5).should == 'hello' + end + + platform_is_not :windows do + it "is set to nonblocking" do + require 'io/nonblock' + @socket = @server.accept + @socket.should.nonblock? + end + end + + it "is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? + end + end + end + end +end diff --git a/spec/ruby/library/socket/unixserver/for_fd_spec.rb b/spec/ruby/library/socket/unixserver/for_fd_spec.rb new file mode 100644 index 0000000000..be1c2df4d7 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/for_fd_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXServer.for_fd" do + before :each do + @unix_path = SocketSpecs.socket_path + @unix = UNIXServer.new(@unix_path) + end + + after :each do + @unix.close if @unix + SocketSpecs.rm_socket @unix_path + end + + it "can calculate the path" do + b = UNIXServer.for_fd(@unix.fileno) + b.autoclose = false + + b.path.should == @unix_path + end +end diff --git a/spec/ruby/library/socket/unixserver/initialize_spec.rb b/spec/ruby/library/socket/unixserver/initialize_spec.rb new file mode 100644 index 0000000000..3728a307b0 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/initialize_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXServer#initialize' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close if @server + rm_r @path + end + + it 'returns a new UNIXServer' do + @server.should be_an_instance_of(UNIXServer) + end + + it 'sets the socket to binmode' do + @server.binmode?.should be_true + end + + it 'raises Errno::EADDRINUSE when the socket is already in use' do + -> { UNIXServer.new(@path) }.should raise_error(Errno::EADDRINUSE) + end +end diff --git a/spec/ruby/library/socket/unixserver/listen_spec.rb b/spec/ruby/library/socket/unixserver/listen_spec.rb new file mode 100644 index 0000000000..7938d648c4 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/listen_spec.rb @@ -0,0 +1,19 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXServer#listen' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + + rm_r(@path) + end + + it 'returns 0' do + @server.listen(1).should == 0 + end +end diff --git a/spec/ruby/library/socket/unixserver/new_spec.rb b/spec/ruby/library/socket/unixserver/new_spec.rb new file mode 100644 index 0000000000..7d0c7bf76e --- /dev/null +++ b/spec/ruby/library/socket/unixserver/new_spec.rb @@ -0,0 +1,12 @@ +require_relative '../spec_helper' +require_relative 'shared/new' + +describe "UNIXServer.new" do + it_behaves_like :unixserver_new, :new + + it "does not use the given block and warns to use UNIXServer::open" do + -> { + @server = UNIXServer.new(@path) { raise } + }.should complain(/warning: UNIXServer::new\(\) does not take block; use UNIXServer::open\(\) instead/) + end +end diff --git a/spec/ruby/library/socket/unixserver/open_spec.rb b/spec/ruby/library/socket/unixserver/open_spec.rb new file mode 100644 index 0000000000..c49df802d0 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/open_spec.rb @@ -0,0 +1,24 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/new' + +describe "UNIXServer.open" do + it_behaves_like :unixserver_new, :open + + before :each do + @path = SocketSpecs.socket_path + end + + after :each do + @server.close if @server + @server = nil + SocketSpecs.rm_socket @path + end + + it "yields the new UNIXServer object to the block, if given" do + UNIXServer.open(@path) do |unix| + unix.path.should == @path + unix.addr.should == ["AF_UNIX", @path] + end + end +end diff --git a/spec/ruby/library/socket/unixserver/shared/new.rb b/spec/ruby/library/socket/unixserver/shared/new.rb new file mode 100644 index 0000000000..b537f2a871 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/shared/new.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :unixserver_new, shared: true do + before :each do + @path = SocketSpecs.socket_path + end + + after :each do + @server.close if @server + @server = nil + SocketSpecs.rm_socket @path + end + + it "creates a new UNIXServer" do + @server = UNIXServer.send(@method, @path) + @server.path.should == @path + @server.addr.should == ["AF_UNIX", @path] + end +end diff --git a/spec/ruby/library/socket/unixserver/sysaccept_spec.rb b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb new file mode 100644 index 0000000000..c4a4ecc824 --- /dev/null +++ b/spec/ruby/library/socket/unixserver/sysaccept_spec.rb @@ -0,0 +1,50 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXServer#sysaccept' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + end + + after do + @server.close + + rm_r(@path) + end + + describe 'without a client' do + it 'blocks the calling thread' do + -> { @server.sysaccept }.should block_caller + end + end + + describe 'with a client' do + before do + @client = UNIXSocket.new(@path) + end + + after do + Socket.for_fd(@fd).close if @fd + @client.close + end + + describe 'without any data' do + it 'returns an Integer' do + @fd = @server.sysaccept + @fd.should be_kind_of(Integer) + end + end + + describe 'with data available' do + before do + @client.write('hello') + end + + it 'returns an Integer' do + @fd = @server.sysaccept + @fd.should be_kind_of(Integer) + end + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/addr_spec.rb b/spec/ruby/library/socket/unixsocket/addr_spec.rb new file mode 100644 index 0000000000..1afe9b12dc --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/addr_spec.rb @@ -0,0 +1,33 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#addr" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "returns an array" do + @client.addr.should be_kind_of(Array) + end + + it "returns the address family of this socket in an array" do + @client.addr[0].should == "AF_UNIX" + @server.addr[0].should == "AF_UNIX" + end + + it "returns the path of the socket in an array if it's a server" do + @server.addr[1].should == @path + end + + it "returns an empty string for path if it's a client" do + @client.addr[1].should == "" + end +end diff --git a/spec/ruby/library/socket/unixsocket/initialize_spec.rb b/spec/ruby/library/socket/unixsocket/initialize_spec.rb new file mode 100644 index 0000000000..5dccfcc745 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/initialize_spec.rb @@ -0,0 +1,56 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXSocket#initialize' do + describe 'using a non existing path' do + platform_is_not :windows do + it 'raises Errno::ENOENT' do + -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ENOENT) + end + end + + platform_is :windows do + # Why, Windows, why? + it 'raises Errno::ECONNREFUSED' do + -> { UNIXSocket.new(SocketSpecs.socket_path) }.should raise_error(Errno::ECONNREFUSED) + end + end + end + + describe 'using an existing socket path' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @socket = UNIXSocket.new(@path) + end + + after do + @socket.close + @server.close + rm_r(@path) + end + + it 'returns a new UNIXSocket' do + @socket.should be_an_instance_of(UNIXSocket) + end + + it 'sets the socket path to an empty String' do + @socket.path.should == '' + end + + it 'sets the socket to binmode' do + @socket.binmode?.should be_true + end + + platform_is_not :windows do + it 'sets the socket to nonblock' do + require 'io/nonblock' + @socket.should.nonblock? + end + end + + it 'sets the socket to close on exec' do + @socket.should.close_on_exec? + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/inspect_spec.rb b/spec/ruby/library/socket/unixsocket/inspect_spec.rb new file mode 100644 index 0000000000..77bb521069 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/inspect_spec.rb @@ -0,0 +1,15 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#inspect" do + it "returns sockets fd for unnamed sockets" do + begin + s1, s2 = UNIXSocket.socketpair + s1.inspect.should == "#<UNIXSocket:fd #{s1.fileno}>" + s2.inspect.should == "#<UNIXSocket:fd #{s2.fileno}>" + ensure + s1.close + s2.close + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/local_address_spec.rb b/spec/ruby/library/socket/unixsocket/local_address_spec.rb new file mode 100644 index 0000000000..0fdec38293 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/local_address_spec.rb @@ -0,0 +1,92 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXSocket#local_address' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + + rm_r(@path) + end + + it 'returns an Addrinfo' do + @client.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + platform_is_not :aix do + it 'uses AF_UNIX as the address family' do + @client.local_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @client.local_address.pfamily.should == Socket::PF_UNIX + end + end + + it 'uses SOCK_STREAM as the socket type' do + @client.local_address.socktype.should == Socket::SOCK_STREAM + end + + platform_is_not :aix do + it 'uses an empty socket path' do + @client.local_address.unix_path.should == '' + end + end + + it 'uses 0 as the protocol' do + @client.local_address.protocol.should == 0 + end + end +end + +describe 'UNIXSocket#local_address with a UNIX socket pair' do + before :each do + @sock, @sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) + end + + after :each do + @sock.close + @sock2.close + end + + it 'returns an Addrinfo' do + @sock.local_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_UNIX as the address family' do + @sock.local_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @sock.local_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @sock.local_address.socktype.should == Socket::SOCK_STREAM + end + + it 'raises SocketError for #ip_address' do + -> { + @sock.local_address.ip_address + }.should raise_error(SocketError, "need IPv4 or IPv6 address") + end + + it 'raises SocketError for #ip_port' do + -> { + @sock.local_address.ip_port + }.should raise_error(SocketError, "need IPv4 or IPv6 address") + end + + it 'uses 0 as the protocol' do + @sock.local_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/new_spec.rb b/spec/ruby/library/socket/unixsocket/new_spec.rb new file mode 100644 index 0000000000..fea2c1e2b7 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/new_spec.rb @@ -0,0 +1,12 @@ +require_relative '../spec_helper' +require_relative 'shared/new' + +describe "UNIXSocket.new" do + it_behaves_like :unixsocket_new, :new + + it "does not use the given block and warns to use UNIXSocket::open" do + -> { + @client = UNIXSocket.new(@path) { raise } + }.should complain(/warning: UNIXSocket::new\(\) does not take block; use UNIXSocket::open\(\) instead/) + end +end diff --git a/spec/ruby/library/socket/unixsocket/open_spec.rb b/spec/ruby/library/socket/unixsocket/open_spec.rb new file mode 100644 index 0000000000..b5e8c6c23a --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/open_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative 'shared/new' + +describe "UNIXSocket.open" do + it_behaves_like :unixsocket_new, :open +end + +describe "UNIXSocket.open" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end + + after :each do + @server.close + SocketSpecs.rm_socket @path + end + + it "opens a unix socket on the specified file and yields it to the block" do + UNIXSocket.open(@path) do |client| + client.addr[0].should == "AF_UNIX" + client.should_not.closed? + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/pair_spec.rb b/spec/ruby/library/socket/unixsocket/pair_spec.rb new file mode 100644 index 0000000000..9690142668 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/pair_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' +require_relative 'shared/pair' + +describe "UNIXSocket.pair" do + it_should_behave_like :unixsocket_pair + it_should_behave_like :partially_closable_sockets + + before :each do + @s1, @s2 = UNIXSocket.pair + end + + after :each do + @s1.close + @s2.close + end +end diff --git a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb new file mode 100644 index 0000000000..108a6c3063 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb @@ -0,0 +1,21 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' + +describe "UNIXSocket partial closability" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @s1 = UNIXSocket.new(@path) + @s2 = @server.accept + end + + after :each do + @server.close + @s1.close + @s2.close + SocketSpecs.rm_socket @path + end + + it_should_behave_like :partially_closable_sockets +end diff --git a/spec/ruby/library/socket/unixsocket/path_spec.rb b/spec/ruby/library/socket/unixsocket/path_spec.rb new file mode 100644 index 0000000000..ffe7e4bea2 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/path_spec.rb @@ -0,0 +1,24 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#path" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "returns the path of the socket if it's a server" do + @server.path.should == @path + end + + it "returns an empty string for path if it's a client" do + @client.path.should == "" + end +end diff --git a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb new file mode 100644 index 0000000000..10cab13b42 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb @@ -0,0 +1,26 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#peeraddr" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "returns the address family and path of the server end of the connection" do + @client.peeraddr.should == ["AF_UNIX", @path] + end + + it "raises an error in server sockets" do + -> { + @server.peeraddr + }.should raise_error(Errno::ENOTCONN) + end +end diff --git a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb new file mode 100644 index 0000000000..f0b408f309 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb @@ -0,0 +1,84 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +platform_is_not :windows do + describe "UNIXSocket#recv_io" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + + @send_io_path = File.expand_path('../../fixtures/send_io.txt', __FILE__) + @file = File.open(@send_io_path) + end + + after :each do + @io.close if @io + @socket.close if @socket + + @file.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "reads an IO object across the socket" do + @client.send_io(@file) + + @socket = @server.accept + @io = @socket.recv_io + + @io.read.should == File.read(@send_io_path) + end + + it "takes an optional class to use" do + @client.send_io(@file) + + @socket = @server.accept + @io = @socket.recv_io(File) + + @io.should be_an_instance_of(File) + end + end + + describe 'UNIXSocket#recv_io' do + before do + @file = File.open('/dev/null', 'w') + @client, @server = UNIXSocket.socketpair + end + + after do + @client.close + @server.close + @io.close if @io + @file.close + end + + describe 'without a custom class' do + it 'returns an IO' do + @client.send_io(@file) + + @io = @server.recv_io + @io.should be_an_instance_of(IO) + end + end + + describe 'with a custom class' do + it 'returns an instance of the custom class' do + @client.send_io(@file) + + @io = @server.recv_io(File) + @io.should be_an_instance_of(File) + end + end + + describe 'with a custom mode' do + it 'opens the IO using the given mode' do + @client.send_io(@file) + + @io = @server.recv_io(File, File::WRONLY) + @io.should be_an_instance_of(File) + end + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb new file mode 100644 index 0000000000..9ae3777961 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb @@ -0,0 +1,174 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "UNIXSocket#recvfrom" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + end + + after :each do + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "receives len bytes from sock, returning an array containing sent data as first element" do + @client.send("foobar", 0) + sock = @server.accept + sock.recvfrom(6).first.should == "foobar" + sock.close + end + + context "when called on a server's socket" do + platform_is_not :windows do + it "returns an array containing basic information on the client as second element" do + @client.send("foobar", 0) + sock = @server.accept + data = sock.recvfrom(6) + data.last.should == ["AF_UNIX", ""] + sock.close + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it "returns an array containing basic information on the client as second element" do + @client.send("foobar", 0) + sock = @server.accept + data = sock.recvfrom(6) + data.last.should == ["AF_UNIX", ""] + sock.close + end + end + end + + context "when called on a client's socket" do + platform_is :linux do + it "returns an array containing server's address as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + @client.recvfrom(6).last.should == ["AF_UNIX", @server.local_address.unix_path] + sock.close + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it "returns an array containing server's address as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + # This may not be correct, depends on what underlying recvfrom actually returns. + @client.recvfrom(6).last.should == ["AF_UNIX", @server.local_address.unix_path] + sock.close + end + end + + platform_is :darwin do + it "returns an array containing basic information on the server as second element" do + @client.send("", 0) + sock = @server.accept + sock.send("barfoo", 0) + @client.recvfrom(6).last.should == ["AF_UNIX", ""] + sock.close + end + end + end + + it "allows an output buffer as third argument" do + buffer = +'' + + @client.send("foobar", 0) + sock = @server.accept + message, = sock.recvfrom(6, 0, buffer) + sock.close + + message.should.equal?(buffer) + buffer.should == "foobar" + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + + @client.send("foobar", 0) + sock = @server.accept + sock.recvfrom(6, 0, buffer) + sock.close + + buffer.encoding.should == Encoding::ISO_8859_1 + end + + platform_is_not :windows do + it "uses different message options" do + @client.send("foobar", Socket::MSG_PEEK) + sock = @server.accept + peek_data = sock.recvfrom(6, Socket::MSG_PEEK) # Does not retrieve the message + real_data = sock.recvfrom(6) + + real_data.should == peek_data + peek_data.should == ["foobar", ["AF_UNIX", ""]] + sock.close + end + end +end + +describe 'UNIXSocket#recvfrom' do + describe 'using a socket pair' do + before do + @client, @server = UNIXSocket.socketpair + @client.write('hello') + end + + after do + @client.close + @server.close + end + + platform_is_not :windows do + it 'returns an Array containing the data and address information' do + @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] + end + end + + guard -> { platform_is :windows and ruby_bug "#21702", ""..."4.2" } do + it 'returns an Array containing the data and address information' do + @server.recvfrom(5).should == ['hello', ['AF_UNIX', '']] + end + end + end + + platform_is_not :windows do + # These specs are taken from the rdoc examples on UNIXSocket#recvfrom. + describe 'using a UNIX socket constructed using UNIXSocket.for_fd' do + before do + @path1 = SocketSpecs.socket_path + @path2 = SocketSpecs.socket_path.chop + '2' + rm_r(@path2) + + @client_raw = Socket.new(:UNIX, :DGRAM) + @client_raw.bind(Socket.sockaddr_un(@path1)) + + @server_raw = Socket.new(:UNIX, :DGRAM) + @server_raw.bind(Socket.sockaddr_un(@path2)) + + @socket = UNIXSocket.for_fd(@server_raw.fileno) + @socket.autoclose = false + end + + after do + @client_raw.close + @server_raw.close # also closes @socket + + rm_r @path1 + rm_r @path2 + end + + it 'returns an Array containing the data and address information' do + @client_raw.send('hello', 0, Socket.sockaddr_un(@path2)) + + @socket.recvfrom(5).should == ['hello', ['AF_UNIX', @path1]] + end + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/remote_address_spec.rb b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb new file mode 100644 index 0000000000..84bdff0a6a --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/remote_address_spec.rb @@ -0,0 +1,43 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe 'UNIXSocket#remote_address' do + before do + @path = SocketSpecs.socket_path + @server = UNIXServer.new(@path) + @client = UNIXSocket.new(@path) + end + + after do + @client.close + @server.close + + rm_r(@path) + end + + it 'returns an Addrinfo' do + @client.remote_address.should be_an_instance_of(Addrinfo) + end + + describe 'the returned Addrinfo' do + it 'uses AF_UNIX as the address family' do + @client.remote_address.afamily.should == Socket::AF_UNIX + end + + it 'uses PF_UNIX as the protocol family' do + @client.remote_address.pfamily.should == Socket::PF_UNIX + end + + it 'uses SOCK_STREAM as the socket type' do + @client.remote_address.socktype.should == Socket::SOCK_STREAM + end + + it 'uses the correct socket path' do + @client.remote_address.unix_path.should == @path + end + + it 'uses 0 as the protocol' do + @client.remote_address.protocol.should == 0 + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/send_io_spec.rb b/spec/ruby/library/socket/unixsocket/send_io_spec.rb new file mode 100644 index 0000000000..52186ec3cf --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/send_io_spec.rb @@ -0,0 +1,55 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +platform_is_not :windows do + describe "UNIXSocket#send_io" do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + @client = UNIXSocket.open(@path) + + @send_io_path = File.expand_path('../../fixtures/send_io.txt', __FILE__) + @file = File.open(@send_io_path) + end + + after :each do + @io.close if @io + @socket.close if @socket + + @file.close + @client.close + @server.close + SocketSpecs.rm_socket @path + end + + it "sends the fd for an IO object across the socket" do + @client.send_io(@file) + + @socket = @server.accept + @io = @socket.recv_io + + @io.read.should == File.read(@send_io_path) + end + end + + describe 'UNIXSocket#send_io' do + before do + @file = File.open('/dev/null', 'w') + @client, @server = UNIXSocket.socketpair + end + + after do + @client.close + @server.close + @io.close if @io + @file.close + end + + it 'sends an IO object' do + @client.send_io(@file) + + @io = @server.recv_io + @io.should be_an_instance_of(IO) + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/shared/new.rb b/spec/ruby/library/socket/unixsocket/shared/new.rb new file mode 100644 index 0000000000..f075b03c5e --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/shared/new.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :unixsocket_new, shared: true do + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end + + after :each do + @client.close if @client + @server.close + SocketSpecs.rm_socket @path + end + + it "opens a unix socket on the specified file" do + @client = UNIXSocket.send(@method, @path) + + @client.addr[0].should == "AF_UNIX" + @client.should_not.closed? + end +end diff --git a/spec/ruby/library/socket/unixsocket/shared/pair.rb b/spec/ruby/library/socket/unixsocket/shared/pair.rb new file mode 100644 index 0000000000..d68ee466bf --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/shared/pair.rb @@ -0,0 +1,47 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/classes' + +describe :unixsocket_pair, shared: true do + it "returns two UNIXSockets" do + @s1.should be_an_instance_of(UNIXSocket) + @s2.should be_an_instance_of(UNIXSocket) + end + + it "returns a pair of connected sockets" do + @s1.puts "foo" + @s2.gets.should == "foo\n" + end + + platform_is_not :windows do + it "sets the socket paths to empty Strings" do + @s1.path.should == "" + @s2.path.should == "" + end + + it "sets the socket addresses to empty Strings" do + @s1.addr.should == ["AF_UNIX", ""] + @s2.addr.should == ["AF_UNIX", ""] + end + + it "sets the socket peer addresses to empty Strings" do + @s1.peeraddr.should == ["AF_UNIX", ""] + @s2.peeraddr.should == ["AF_UNIX", ""] + end + end + + platform_is :windows do + it "emulates unnamed sockets with a temporary file with a path" do + @s1.addr.should == ["AF_UNIX", @s1.path] + @s2.peeraddr.should == ["AF_UNIX", @s1.path] + end + + it "sets the peer address of first socket to an empty string" do + @s1.peeraddr.should == ["AF_UNIX", ""] + end + + it "sets the address and path of second socket to an empty string" do + @s2.addr.should == ["AF_UNIX", ""] + @s2.path.should == "" + end + end +end diff --git a/spec/ruby/library/socket/unixsocket/socketpair_spec.rb b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb new file mode 100644 index 0000000000..c61fc00be4 --- /dev/null +++ b/spec/ruby/library/socket/unixsocket/socketpair_spec.rb @@ -0,0 +1,18 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/partially_closable_sockets' +require_relative 'shared/pair' + +describe "UNIXSocket.socketpair" do + it_should_behave_like :unixsocket_pair + it_should_behave_like :partially_closable_sockets + + before :each do + @s1, @s2 = UNIXSocket.socketpair + end + + after :each do + @s1.close + @s2.close + end +end diff --git a/spec/ruby/library/stringio/append_spec.rb b/spec/ruby/library/stringio/append_spec.rb new file mode 100644 index 0000000000..cb50d73d1b --- /dev/null +++ b/spec/ruby/library/stringio/append_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#<< when passed [Object]" do + before :each do + @io = StringIO.new(+"example") + end + + it "returns self" do + (@io << "just testing").should equal(@io) + end + + it "writes the passed argument onto self" do + (@io << "just testing") + @io.string.should == "just testing" + (@io << " and more testing") + @io.string.should == "just testing and more testing" + end + + it "writes the passed argument at the current position" do + @io.pos = 5 + @io << "<test>" + @io.string.should == "examp<test>" + end + + it "pads self with \\000 when the current position is after the end" do + @io.pos = 15 + @io << "just testing" + @io.string.should == "example\000\000\000\000\000\000\000\000just testing" + end + + it "updates self's position" do + @io << "test" + @io.pos.should eql(4) + end + + it "tries to convert the passed argument to a String using #to_s" do + obj = mock("to_s") + obj.should_receive(:to_s).and_return("Test") + + (@io << obj).string.should == "Testple" + end +end + +describe "StringIO#<< when self is not writable" do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io << "test" }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io << "test" }.should raise_error(IOError) + end +end + +describe "StringIO#<< when in append mode" do + before :each do + @io = StringIO.new(+"example", "a") + end + + it "appends the passed argument to the end of self, ignoring current position" do + (@io << ", just testing") + @io.string.should == "example, just testing" + + @io.pos = 3 + (@io << " and more testing") + @io.string.should == "example, just testing and more testing" + end + + it "correctly updates self's position" do + @io << ", testing" + @io.pos.should eql(16) + end +end diff --git a/spec/ruby/library/stringio/binmode_spec.rb b/spec/ruby/library/stringio/binmode_spec.rb new file mode 100644 index 0000000000..9e92c63814 --- /dev/null +++ b/spec/ruby/library/stringio/binmode_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#binmode" do + it "returns self" do + io = StringIO.new(+"example") + io.binmode.should equal(io) + end + + it "changes external encoding to BINARY" do + io = StringIO.new + io.external_encoding.should == Encoding.find('external') + io.binmode + io.external_encoding.should == Encoding::BINARY + end + + it "does not set internal encoding" do + io = StringIO.new + io.internal_encoding.should == nil + io.binmode + io.internal_encoding.should == nil + end +end diff --git a/spec/ruby/library/stringio/close_read_spec.rb b/spec/ruby/library/stringio/close_read_spec.rb new file mode 100644 index 0000000000..0f08e1ff2e --- /dev/null +++ b/spec/ruby/library/stringio/close_read_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#close_read" do + before :each do + @io = StringIO.new(+"example") + end + + it "returns nil" do + @io.close_read.should be_nil + end + + it "prevents further reading" do + @io.close_read + -> { @io.read(1) }.should raise_error(IOError) + end + + it "allows further writing" do + @io.close_read + @io.write("x").should == 1 + end + + it "raises an IOError when in write-only mode" do + io = StringIO.new(+"example", "w") + -> { io.close_read }.should raise_error(IOError) + + io = StringIO.new("example") + io.close_read + io.close_read.should == nil + end +end diff --git a/spec/ruby/library/stringio/close_spec.rb b/spec/ruby/library/stringio/close_spec.rb new file mode 100644 index 0000000000..520a8de782 --- /dev/null +++ b/spec/ruby/library/stringio/close_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#close" do + before :each do + @io = StringIOSpecs.build + end + + it "returns nil" do + @io.close.should be_nil + end + + it "prevents further reading and/or writing" do + @io.close + -> { @io.read(1) }.should raise_error(IOError) + -> { @io.write('x') }.should raise_error(IOError) + end + + it "does not raise anything when self was already closed" do + @io.close + -> { @io.close }.should_not raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/close_write_spec.rb b/spec/ruby/library/stringio/close_write_spec.rb new file mode 100644 index 0000000000..c86c3f9826 --- /dev/null +++ b/spec/ruby/library/stringio/close_write_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#close_write" do + before :each do + @io = StringIO.new(+"example") + end + + it "returns nil" do + @io.close_write.should be_nil + end + + it "prevents further writing" do + @io.close_write + -> { @io.write('x') }.should raise_error(IOError) + end + + it "allows further reading" do + @io.close_write + @io.read(1).should == 'e' + end + + it "raises an IOError when in read-only mode" do + io = StringIO.new(+"example", "r") + -> { io.close_write }.should raise_error(IOError) + + io = StringIO.new(+"example") + io.close_write + io.close_write.should == nil + end +end diff --git a/spec/ruby/library/stringio/closed_read_spec.rb b/spec/ruby/library/stringio/closed_read_spec.rb new file mode 100644 index 0000000000..b4dcadc3a4 --- /dev/null +++ b/spec/ruby/library/stringio/closed_read_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#closed_read?" do + it "returns true if self is not readable" do + io = StringIO.new(+"example", "r+") + io.close_write + io.closed_read?.should be_false + io.close_read + io.closed_read?.should be_true + end +end diff --git a/spec/ruby/library/stringio/closed_spec.rb b/spec/ruby/library/stringio/closed_spec.rb new file mode 100644 index 0000000000..bf7ba63184 --- /dev/null +++ b/spec/ruby/library/stringio/closed_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#closed?" do + it "returns true if self is completely closed" do + io = StringIO.new(+"example", "r+") + io.close_read + io.closed?.should be_false + io.close_write + io.closed?.should be_true + + io = StringIO.new(+"example", "r+") + io.close + io.closed?.should be_true + end +end diff --git a/spec/ruby/library/stringio/closed_write_spec.rb b/spec/ruby/library/stringio/closed_write_spec.rb new file mode 100644 index 0000000000..2bd3e6fa8b --- /dev/null +++ b/spec/ruby/library/stringio/closed_write_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#closed_write?" do + it "returns true if self is not writable" do + io = StringIO.new(+"example", "r+") + io.close_read + io.closed_write?.should be_false + io.close_write + io.closed_write?.should be_true + end +end diff --git a/spec/ruby/library/stringio/each_byte_spec.rb b/spec/ruby/library/stringio/each_byte_spec.rb new file mode 100644 index 0000000000..6f82a32441 --- /dev/null +++ b/spec/ruby/library/stringio/each_byte_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/each_byte' + +describe "StringIO#each_byte" do + it_behaves_like :stringio_each_byte, :each_byte +end + +describe "StringIO#each_byte when self is not readable" do + it_behaves_like :stringio_each_byte_not_readable, :each_byte +end diff --git a/spec/ruby/library/stringio/each_char_spec.rb b/spec/ruby/library/stringio/each_char_spec.rb new file mode 100644 index 0000000000..14b2f09a17 --- /dev/null +++ b/spec/ruby/library/stringio/each_char_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/each_char' + +describe "StringIO#each_char" do + it_behaves_like :stringio_each_char, :each_char +end + +describe "StringIO#each_char when self is not readable" do + it_behaves_like :stringio_each_char_not_readable, :each_char +end diff --git a/spec/ruby/library/stringio/each_codepoint_spec.rb b/spec/ruby/library/stringio/each_codepoint_spec.rb new file mode 100644 index 0000000000..f18de22aad --- /dev/null +++ b/spec/ruby/library/stringio/each_codepoint_spec.rb @@ -0,0 +1,9 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/codepoints' + +# See redmine #1667 +describe "StringIO#each_codepoint" do + it_behaves_like :stringio_codepoints, :each_codepoint +end diff --git a/spec/ruby/library/stringio/each_line_spec.rb b/spec/ruby/library/stringio/each_line_spec.rb new file mode 100644 index 0000000000..4ac0db7c45 --- /dev/null +++ b/spec/ruby/library/stringio/each_line_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/each' + +describe "StringIO#each_line when passed a separator" do + it_behaves_like :stringio_each_separator, :each_line +end + +describe "StringIO#each_line when passed no arguments" do + it_behaves_like :stringio_each_no_arguments, :each_line +end + +describe "StringIO#each_line when self is not readable" do + it_behaves_like :stringio_each_not_readable, :each_line +end + +describe "StringIO#each_line when passed chomp" do + it_behaves_like :stringio_each_chomp, :each_line +end + +describe "StringIO#each_line when passed limit" do + it_behaves_like :stringio_each_limit, :each_line +end + +describe "StringIO#each when passed separator and limit" do + it_behaves_like :stringio_each_separator_and_limit, :each_line +end diff --git a/spec/ruby/library/stringio/each_spec.rb b/spec/ruby/library/stringio/each_spec.rb new file mode 100644 index 0000000000..7eb322f3ff --- /dev/null +++ b/spec/ruby/library/stringio/each_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/each' + +describe "StringIO#each when passed a separator" do + it_behaves_like :stringio_each_separator, :each +end + +describe "StringIO#each when passed no arguments" do + it_behaves_like :stringio_each_no_arguments, :each +end + +describe "StringIO#each when self is not readable" do + it_behaves_like :stringio_each_not_readable, :each +end + +describe "StringIO#each when passed chomp" do + it_behaves_like :stringio_each_chomp, :each +end + +describe "StringIO#each when passed chomp" do + it_behaves_like :stringio_each_separator_and_chomp, :each +end + +describe "StringIO#each when passed limit" do + it_behaves_like :stringio_each_limit, :each +end + +describe "StringIO#each when passed separator and limit" do + it_behaves_like :stringio_each_separator_and_limit, :each +end diff --git a/spec/ruby/library/stringio/eof_spec.rb b/spec/ruby/library/stringio/eof_spec.rb new file mode 100644 index 0000000000..af0170977c --- /dev/null +++ b/spec/ruby/library/stringio/eof_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/eof' + +describe "StringIO#eof?" do + it_behaves_like :stringio_eof, :eof? +end + +describe "StringIO#eof" do + it_behaves_like :stringio_eof, :eof +end diff --git a/spec/ruby/library/stringio/external_encoding_spec.rb b/spec/ruby/library/stringio/external_encoding_spec.rb new file mode 100644 index 0000000000..6c5edb1713 --- /dev/null +++ b/spec/ruby/library/stringio/external_encoding_spec.rb @@ -0,0 +1,25 @@ +require 'stringio' +require_relative '../../spec_helper' + +describe "StringIO#external_encoding" do + it "gets the encoding of the underlying String" do + io = StringIO.new + io.set_encoding Encoding::EUC_JP + io.external_encoding.should == Encoding::EUC_JP + end + + it "changes to match string if string's encoding is changed" do + io = StringIO.new + io.string.force_encoding(Encoding::EUC_JP) + io.external_encoding.should == Encoding::EUC_JP + end + + it "does not set the encoding of its buffer string if the string is frozen" do + str = "foo".freeze + enc = str.encoding + io = StringIO.new(str) + io.set_encoding Encoding::EUC_JP + io.external_encoding.should == Encoding::EUC_JP + str.encoding.should == enc + end +end diff --git a/spec/ruby/library/stringio/fcntl_spec.rb b/spec/ruby/library/stringio/fcntl_spec.rb new file mode 100644 index 0000000000..a78004d868 --- /dev/null +++ b/spec/ruby/library/stringio/fcntl_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#fcntl" do + it "raises a NotImplementedError" do + -> { StringIO.new("boom").fcntl }.should raise_error(NotImplementedError) + end +end diff --git a/spec/ruby/library/stringio/fileno_spec.rb b/spec/ruby/library/stringio/fileno_spec.rb new file mode 100644 index 0000000000..5a9f440a0f --- /dev/null +++ b/spec/ruby/library/stringio/fileno_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'stringio' + +describe "StringIO#fileno" do + it "returns nil" do + StringIO.new("nuffin").fileno.should be_nil + end +end diff --git a/spec/ruby/library/stringio/fixtures/classes.rb b/spec/ruby/library/stringio/fixtures/classes.rb new file mode 100644 index 0000000000..832c5457d7 --- /dev/null +++ b/spec/ruby/library/stringio/fixtures/classes.rb @@ -0,0 +1,15 @@ +require 'stringio' + +class StringSubclass < String; end + +module StringIOSpecs + def self.build + str = <<-EOS + each + peach + pear + plum + EOS + StringIO.new(str) + end +end diff --git a/spec/ruby/library/stringio/flush_spec.rb b/spec/ruby/library/stringio/flush_spec.rb new file mode 100644 index 0000000000..4dc58b1d48 --- /dev/null +++ b/spec/ruby/library/stringio/flush_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#flush" do + it "returns self" do + io = StringIO.new(+"flush") + io.flush.should equal(io) + end +end diff --git a/spec/ruby/library/stringio/fsync_spec.rb b/spec/ruby/library/stringio/fsync_spec.rb new file mode 100644 index 0000000000..85053cb2e5 --- /dev/null +++ b/spec/ruby/library/stringio/fsync_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#fsync" do + it "returns zero" do + io = StringIO.new(+"fsync") + io.fsync.should eql(0) + end +end diff --git a/spec/ruby/library/stringio/getbyte_spec.rb b/spec/ruby/library/stringio/getbyte_spec.rb new file mode 100644 index 0000000000..3daa3d8e02 --- /dev/null +++ b/spec/ruby/library/stringio/getbyte_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/getc' + +describe "StringIO#getbyte" do + it_behaves_like :stringio_getc, :getbyte + + it "returns the 8-bit byte at the current position" do + io = StringIO.new("example") + + io.getbyte.should == 101 + io.getbyte.should == 120 + io.getbyte.should == 97 + end +end + +describe "StringIO#getbyte when self is not readable" do + it_behaves_like :stringio_getc_not_readable, :getbyte +end diff --git a/spec/ruby/library/stringio/getc_spec.rb b/spec/ruby/library/stringio/getc_spec.rb new file mode 100644 index 0000000000..263d418316 --- /dev/null +++ b/spec/ruby/library/stringio/getc_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/getc' + +describe "StringIO#getc" do + it_behaves_like :stringio_getc, :getc + + it "returns the character at the current position" do + io = StringIO.new("example") + + io.getc.should == ?e + io.getc.should == ?x + io.getc.should == ?a + end +end + +describe "StringIO#getc when self is not readable" do + it_behaves_like :stringio_getc_not_readable, :getc +end diff --git a/spec/ruby/library/stringio/getch_spec.rb b/spec/ruby/library/stringio/getch_spec.rb new file mode 100644 index 0000000000..113b4971bf --- /dev/null +++ b/spec/ruby/library/stringio/getch_spec.rb @@ -0,0 +1,44 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/getc' + +# This method is added by io/console on require. +describe "StringIO#getch" do + require 'io/console' + + it_behaves_like :stringio_getc, :getch + + it "returns the character at the current position" do + io = StringIO.new("example") + + io.getch.should == ?e + io.getch.should == ?x + io.getch.should == ?a + end + + it "increments #pos by the byte size of the character in multibyte strings" do + io = StringIO.new("föóbar") + + io.getch; io.pos.should == 1 # "f" has byte size 1 + io.getch; io.pos.should == 3 # "ö" has byte size 2 + io.getch; io.pos.should == 5 # "ó" has byte size 2 + io.getch; io.pos.should == 6 # "b" has byte size 1 + end + + it "returns nil at the end of the string" do + # empty string case + io = StringIO.new("") + io.getch.should == nil + io.getch.should == nil + + # non-empty string case + io = StringIO.new("a") + io.getch # skip one + io.getch.should == nil + end + + describe "StringIO#getch when self is not readable" do + it_behaves_like :stringio_getc_not_readable, :getch + end +end diff --git a/spec/ruby/library/stringio/getpass_spec.rb b/spec/ruby/library/stringio/getpass_spec.rb new file mode 100644 index 0000000000..60fc64f0c5 --- /dev/null +++ b/spec/ruby/library/stringio/getpass_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require 'stringio' + +# This method is added by io/console on require. +describe "StringIO#getpass" do + require 'io/console' + + it "is defined by io/console" do + StringIO.new("example").should.respond_to?(:getpass) + end +end diff --git a/spec/ruby/library/stringio/gets_spec.rb b/spec/ruby/library/stringio/gets_spec.rb new file mode 100644 index 0000000000..ac876f0b4f --- /dev/null +++ b/spec/ruby/library/stringio/gets_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../spec_helper' +require "stringio" +require_relative "shared/gets" + +describe "StringIO#gets" do + describe "when passed [separator]" do + it_behaves_like :stringio_gets_separator, :gets + + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + @io.gets(">").should be_nil + @io.gets(">").should be_nil + end + end + + describe "when passed [limit]" do + it_behaves_like :stringio_gets_limit, :gets + + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + @io.gets(3).should be_nil + @io.gets(3).should be_nil + end + end + + describe "when passed [separator] and [limit]" do + it_behaves_like :stringio_gets_separator_and_limit, :gets + + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + @io.gets(">", 3).should be_nil + @io.gets(">", 3).should be_nil + end + end + + describe "when passed no argument" do + it_behaves_like :stringio_gets_no_argument, :gets + + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + @io.gets.should be_nil + @io.gets.should be_nil + end + end + + describe "when passed [chomp]" do + it_behaves_like :stringio_gets_chomp, :gets + end + + describe "when in write-only mode" do + it_behaves_like :stringio_gets_write_only, :gets + end +end diff --git a/spec/ruby/library/stringio/initialize_spec.rb b/spec/ruby/library/stringio/initialize_spec.rb new file mode 100644 index 0000000000..6f4d2e456c --- /dev/null +++ b/spec/ruby/library/stringio/initialize_spec.rb @@ -0,0 +1,328 @@ +require_relative '../../spec_helper' +require 'stringio' + +describe "StringIO#initialize when passed [Object, mode]" do + before :each do + @io = StringIO.allocate + end + + it "uses the passed Object as the StringIO backend" do + @io.send(:initialize, str = "example", "r") + @io.string.should equal(str) + end + + it "sets the mode based on the passed mode" do + io = StringIO.allocate + io.send(:initialize, +"example", "r") + io.closed_read?.should be_false + io.closed_write?.should be_true + + io = StringIO.allocate + io.send(:initialize, +"example", "rb") + io.closed_read?.should be_false + io.closed_write?.should be_true + + io = StringIO.allocate + io.send(:initialize, +"example", "r+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "rb+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "w") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "wb") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "w+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "wb+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "a") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "ab") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "a+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", "ab+") + io.closed_read?.should be_false + io.closed_write?.should be_false + end + + it "allows passing the mode as an Integer" do + io = StringIO.allocate + io.send(:initialize, +"example", IO::RDONLY) + io.closed_read?.should be_false + io.closed_write?.should be_true + + io = StringIO.allocate + io.send(:initialize, +"example", IO::RDWR) + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", IO::WRONLY) + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", IO::WRONLY | IO::TRUNC) + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", IO::RDWR | IO::TRUNC) + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", IO::WRONLY | IO::APPEND) + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.allocate + io.send(:initialize, +"example", IO::RDWR | IO::APPEND) + io.closed_read?.should be_false + io.closed_write?.should be_false + end + + it "raises a FrozenError when passed a frozen String in truncate mode as StringIO backend" do + io = StringIO.allocate + -> { io.send(:initialize, "example".freeze, IO::TRUNC) }.should raise_error(FrozenError) + end + + it "tries to convert the passed mode to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return("r") + @io.send(:initialize, +"example", obj) + + @io.closed_read?.should be_false + @io.closed_write?.should be_true + end + + it "raises an Errno::EACCES error when passed a frozen string with a write-mode" do + (str = "example").freeze + -> { @io.send(:initialize, str, "r+") }.should raise_error(Errno::EACCES) + -> { @io.send(:initialize, str, "w") }.should raise_error(Errno::EACCES) + -> { @io.send(:initialize, str, "a") }.should raise_error(Errno::EACCES) + end + + it "truncates all the content if passed w mode" do + io = StringIO.allocate + source = +"example".encode(Encoding::ISO_8859_1); + + io.send(:initialize, source, "w") + + io.string.should.empty? + io.string.encoding.should == Encoding::ISO_8859_1 + end + + it "truncates all the content if passed IO::TRUNC mode" do + io = StringIO.allocate + source = +"example".encode(Encoding::ISO_8859_1); + + io.send(:initialize, source, IO::TRUNC) + + io.string.should.empty? + io.string.encoding.should == Encoding::ISO_8859_1 + end +end + +describe "StringIO#initialize when passed [Object]" do + before :each do + @io = StringIO.allocate + end + + it "uses the passed Object as the StringIO backend" do + @io.send(:initialize, str = "example") + @io.string.should equal(str) + end + + it "sets the mode to read-write if the string is mutable" do + @io.send(:initialize, +"example") + @io.closed_read?.should be_false + @io.closed_write?.should be_false + end + + it "sets the mode to read if the string is frozen" do + @io.send(:initialize, -"example") + @io.closed_read?.should be_false + @io.closed_write?.should be_true + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return("example") + @io.send(:initialize, obj) + @io.string.should == "example" + end + + it "automatically sets the mode to read-only when passed a frozen string" do + (str = "example").freeze + @io.send(:initialize, str) + @io.closed_read?.should be_false + @io.closed_write?.should be_true + end +end + +# NOTE: Synchronise with core/io/new_spec.rb (core/io/shared/new.rb) +describe "StringIO#initialize when passed keyword arguments" do + it "sets the mode based on the passed :mode option" do + io = StringIO.new("example", mode: "r") + io.closed_read?.should be_false + io.closed_write?.should be_true + end + + it "accepts a mode argument set to nil with a valid :mode option" do + @io = StringIO.new(+'', nil, mode: "w") + @io.write("foo").should == 3 + end + + it "accepts a mode argument with a :mode option set to nil" do + @io = StringIO.new(+'', "w", mode: nil) + @io.write("foo").should == 3 + end + + it "sets binmode from :binmode option" do + @io = StringIO.new(+'', 'w', binmode: true) + @io.external_encoding.to_s.should == "ASCII-8BIT" # #binmode? isn't implemented in StringIO + end + + it "does not set binmode from false :binmode" do + @io = StringIO.new(+'', 'w', binmode: false) + @io.external_encoding.to_s.should == "UTF-8" # #binmode? isn't implemented in StringIO + end +end + +# NOTE: Synchronise with core/io/new_spec.rb (core/io/shared/new.rb) +describe "StringIO#initialize when passed keyword arguments and error happens" do + it "raises an error if passed encodings two ways" do + -> { + @io = StringIO.new(+'', 'w:ISO-8859-1', encoding: 'ISO-8859-1') + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', 'w:ISO-8859-1', external_encoding: 'ISO-8859-1') + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1') + }.should raise_error(ArgumentError) + end + + it "raises an error if passed matching binary/text mode two ways" do + -> { + @io = StringIO.new(+'', "wb", binmode: true) + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', "wt", textmode: true) + }.should raise_error(ArgumentError) + + -> { + @io = StringIO.new(+'', "wb", textmode: false) + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', "wt", binmode: false) + }.should raise_error(ArgumentError) + end + + it "raises an error if passed conflicting binary/text mode two ways" do + -> { + @io = StringIO.new(+'', "wb", binmode: false) + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', "wt", textmode: false) + }.should raise_error(ArgumentError) + + -> { + @io = StringIO.new(+'', "wb", textmode: true) + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', "wt", binmode: true) + }.should raise_error(ArgumentError) + end + + it "raises an error when trying to set both binmode and textmode" do + -> { + @io = StringIO.new(+'', "w", textmode: true, binmode: true) + }.should raise_error(ArgumentError) + -> { + @io = StringIO.new(+'', File::Constants::WRONLY, textmode: true, binmode: true) + }.should raise_error(ArgumentError) + end +end + +describe "StringIO#initialize when passed no arguments" do + before :each do + @io = StringIO.allocate + end + + it "is private" do + StringIO.should have_private_instance_method(:initialize) + end + + it "sets the mode to read-write" do + @io.send(:initialize) + @io.closed_read?.should be_false + @io.closed_write?.should be_false + end + + it "uses an empty String as the StringIO backend" do + @io.send(:initialize) + @io.string.should == "" + end +end + +describe "StringIO#initialize sets" do + before :each do + @external = Encoding.default_external + @internal = Encoding.default_internal + Encoding.default_external = Encoding::ISO_8859_2 + Encoding.default_internal = Encoding::ISO_8859_2 + end + + after :each do + Encoding.default_external = @external + Encoding.default_internal = @internal + end + + it "the encoding to Encoding.default_external when passed no arguments" do + io = StringIO.new + io.external_encoding.should == Encoding::ISO_8859_2 + io.string.encoding.should == Encoding::ISO_8859_2 + end + + it "the encoding to the encoding of the String when passed a String" do + s = ''.dup.force_encoding(Encoding::EUC_JP) + io = StringIO.new(s) + io.string.encoding.should == Encoding::EUC_JP + end + + it "the #external_encoding to the encoding of the String when passed a String" do + s = ''.dup.force_encoding(Encoding::EUC_JP) + io = StringIO.new(s) + io.external_encoding.should == Encoding::EUC_JP + end +end diff --git a/spec/ruby/library/stringio/inspect_spec.rb b/spec/ruby/library/stringio/inspect_spec.rb new file mode 100644 index 0000000000..7c02f8d360 --- /dev/null +++ b/spec/ruby/library/stringio/inspect_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require "stringio" + +describe "StringIO#inspect" do + it "returns the same as #to_s" do + io = StringIO.new("example") + io.inspect.should == io.to_s + end + + it "does not include the contents" do + io = StringIO.new("contents") + io.inspect.should_not include("contents") + end + + it "uses the regular Object#inspect without any instance variable" do + io = StringIO.new("example") + io.inspect.should =~ /\A#<StringIO:0x\h+>\z/ + end +end diff --git a/spec/ruby/library/stringio/internal_encoding_spec.rb b/spec/ruby/library/stringio/internal_encoding_spec.rb new file mode 100644 index 0000000000..2035cf25a9 --- /dev/null +++ b/spec/ruby/library/stringio/internal_encoding_spec.rb @@ -0,0 +1,10 @@ +require 'stringio' +require_relative '../../spec_helper' + +describe "StringIO#internal_encoding" do + it "returns nil" do + io = StringIO.new + io.set_encoding Encoding::UTF_8 + io.internal_encoding.should == nil + end +end diff --git a/spec/ruby/library/stringio/isatty_spec.rb b/spec/ruby/library/stringio/isatty_spec.rb new file mode 100644 index 0000000000..1ef33978b5 --- /dev/null +++ b/spec/ruby/library/stringio/isatty_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/isatty' + +describe "StringIO#isatty" do + it_behaves_like :stringio_isatty, :isatty +end diff --git a/spec/ruby/library/stringio/length_spec.rb b/spec/ruby/library/stringio/length_spec.rb new file mode 100644 index 0000000000..d3070f50a7 --- /dev/null +++ b/spec/ruby/library/stringio/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/length' + +describe "StringIO#length" do + it_behaves_like :stringio_length, :length +end diff --git a/spec/ruby/library/stringio/lineno_spec.rb b/spec/ruby/library/stringio/lineno_spec.rb new file mode 100644 index 0000000000..c620a1a686 --- /dev/null +++ b/spec/ruby/library/stringio/lineno_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require "stringio" + +describe "StringIO#lineno" do + before :each do + @io = StringIO.new("this\nis\nan\nexample") + end + + it "returns the number of lines read" do + @io.gets + @io.gets + @io.gets + @io.lineno.should eql(3) + end +end + +describe "StringIO#lineno=" do + before :each do + @io = StringIO.new("this\nis\nan\nexample") + end + + it "sets the current line number, but has no impact on the position" do + @io.lineno = 3 + @io.pos.should eql(0) + + @io.gets.should == "this\n" + @io.lineno.should eql(4) + @io.pos.should eql(5) + end +end diff --git a/spec/ruby/library/stringio/new_spec.rb b/spec/ruby/library/stringio/new_spec.rb new file mode 100644 index 0000000000..ec4b32aa11 --- /dev/null +++ b/spec/ruby/library/stringio/new_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'stringio' + +describe "StringIO.new" do + it "does not use the given block and warns to use StringIO::open" do + -> { + StringIO.new { raise } + }.should complain(/warning: StringIO::new\(\) does not take block; use StringIO::open\(\) instead/) + end +end diff --git a/spec/ruby/library/stringio/open_spec.rb b/spec/ruby/library/stringio/open_spec.rb new file mode 100644 index 0000000000..b7c90661f9 --- /dev/null +++ b/spec/ruby/library/stringio/open_spec.rb @@ -0,0 +1,215 @@ +require_relative '../../spec_helper' +require 'stringio' + +describe "StringIO.open when passed [Object, mode]" do + it "uses the passed Object as the StringIO backend" do + io = StringIO.open(str = "example", "r") + io.string.should equal(str) + end + + it "returns the blocks return value when yielding" do + ret = StringIO.open(+"example", "r") { :test } + ret.should equal(:test) + end + + it "yields self to the passed block" do + io = nil + StringIO.open(+"example", "r") { |strio| io = strio } + io.should be_kind_of(StringIO) + end + + it "closes self after yielding" do + io = nil + StringIO.open(+"example", "r") { |strio| io = strio } + io.closed?.should be_true + end + + it "even closes self when an exception is raised while yielding" do + io = nil + begin + StringIO.open(+"example", "r") do |strio| + io = strio + raise "Error" + end + rescue + end + io.closed?.should be_true + end + + it "sets self's string to nil after yielding" do + io = nil + StringIO.open(+"example", "r") { |strio| io = strio } + io.string.should be_nil + end + + it "even sets self's string to nil when an exception is raised while yielding" do + io = nil + begin + StringIO.open(+"example", "r") do |strio| + io = strio + raise "Error" + end + rescue + end + io.string.should be_nil + end + + it "sets the mode based on the passed mode" do + io = StringIO.open(+"example", "r") + io.closed_read?.should be_false + io.closed_write?.should be_true + + io = StringIO.open(+"example", "rb") + io.closed_read?.should be_false + io.closed_write?.should be_true + + io = StringIO.open(+"example", "r+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", "rb+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", "w") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", "wb") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", "w+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", "wb+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", "a") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", "ab") + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", "a+") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", "ab+") + io.closed_read?.should be_false + io.closed_write?.should be_false + end + + it "allows passing the mode as an Integer" do + io = StringIO.open(+"example", IO::RDONLY) + io.closed_read?.should be_false + io.closed_write?.should be_true + + io = StringIO.open(+"example", IO::RDWR) + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", IO::WRONLY) + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", IO::WRONLY | IO::TRUNC) + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", IO::RDWR | IO::TRUNC) + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.open(+"example", IO::WRONLY | IO::APPEND) + io.closed_read?.should be_true + io.closed_write?.should be_false + + io = StringIO.open(+"example", IO::RDWR | IO::APPEND) + io.closed_read?.should be_false + io.closed_write?.should be_false + end + + it "raises a FrozenError when passed a frozen String in truncate mode as StringIO backend" do + -> { StringIO.open("example".freeze, IO::TRUNC) }.should raise_error(FrozenError) + end + + it "tries to convert the passed mode to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return("r") + io = StringIO.open(+"example", obj) + + io.closed_read?.should be_false + io.closed_write?.should be_true + end + + it "raises an Errno::EACCES error when passed a frozen string with a write-mode" do + (str = "example").freeze + -> { StringIO.open(str, "r+") }.should raise_error(Errno::EACCES) + -> { StringIO.open(str, "w") }.should raise_error(Errno::EACCES) + -> { StringIO.open(str, "a") }.should raise_error(Errno::EACCES) + end +end + +describe "StringIO.open when passed [Object]" do + it "uses the passed Object as the StringIO backend" do + io = StringIO.open(str = "example") + io.string.should equal(str) + end + + it "yields self to the passed block" do + io = nil + ret = StringIO.open(+"example") { |strio| io = strio } + io.should equal(ret) + end + + it "sets the mode to read-write (r+)" do + io = StringIO.open(+"example") + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.new(+"example") + io.printf("%d", 123) + io.string.should == "123mple" + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return("example") + io = StringIO.open(obj) + io.string.should == "example" + end + + it "automatically sets the mode to read-only when passed a frozen string" do + (str = "example").freeze + io = StringIO.open(str) + io.closed_read?.should be_false + io.closed_write?.should be_true + end +end + +describe "StringIO.open when passed no arguments" do + it "yields self to the passed block" do + io = nil + ret = StringIO.open { |strio| io = strio } + io.should equal(ret) + end + + it "sets the mode to read-write (r+)" do + io = StringIO.open + io.closed_read?.should be_false + io.closed_write?.should be_false + + io = StringIO.new(+"example") + io.printf("%d", 123) + io.string.should == "123mple" + end + + it "uses an empty String as the StringIO backend" do + StringIO.open.string.should == "" + end +end diff --git a/spec/ruby/library/stringio/path_spec.rb b/spec/ruby/library/stringio/path_spec.rb new file mode 100644 index 0000000000..1184ca523f --- /dev/null +++ b/spec/ruby/library/stringio/path_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#path" do + it "is not defined" do + -> { StringIO.new("path").path }.should raise_error(NoMethodError) + end +end diff --git a/spec/ruby/library/stringio/pid_spec.rb b/spec/ruby/library/stringio/pid_spec.rb new file mode 100644 index 0000000000..08f2d7ab1a --- /dev/null +++ b/spec/ruby/library/stringio/pid_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#pid" do + it "returns nil" do + StringIO.new("pid").pid.should be_nil + end +end diff --git a/spec/ruby/library/stringio/pos_spec.rb b/spec/ruby/library/stringio/pos_spec.rb new file mode 100644 index 0000000000..81be5f01a5 --- /dev/null +++ b/spec/ruby/library/stringio/pos_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/tell' + +describe "StringIO#pos" do + it_behaves_like :stringio_tell, :pos +end + +describe "StringIO#pos=" do + before :each do + @io = StringIOSpecs.build + end + + it "updates the current byte offset" do + @io.pos = 26 + @io.read(1).should == "r" + end + + it "raises an EINVAL if given a negative argument" do + -> { @io.pos = -10 }.should raise_error(Errno::EINVAL) + end + + it "updates the current byte offset after reaching EOF" do + @io.read + @io.pos = 26 + @io.read(1).should == "r" + end +end diff --git a/spec/ruby/library/stringio/print_spec.rb b/spec/ruby/library/stringio/print_spec.rb new file mode 100644 index 0000000000..00c33367dc --- /dev/null +++ b/spec/ruby/library/stringio/print_spec.rb @@ -0,0 +1,102 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#print" do + before :each do + @io = StringIO.new(+'example') + end + + it "prints $_ when passed no arguments" do + $_ = nil + @io.print + @io.string.should == "example" + + $_ = "blah" + @io.print + @io.string.should == "blahple" + end + + it "prints the passed arguments to self" do + @io.print(5, 6, 7, 8) + @io.string.should == "5678ple" + end + + it "tries to convert the passed Object to a String using #to_s" do + obj = mock("to_s") + obj.should_receive(:to_s).and_return("to_s") + @io.print(obj) + @io.string.should == "to_sple" + end + + it "returns nil" do + @io.print(1, 2, 3).should be_nil + end + + it "pads self with \\000 when the current position is after the end" do + @io.pos = 10 + @io.print(1, 2, 3) + @io.string.should == "example\000\000\000123" + end + + it "honors the output record separator global" do + old_rs = $\ + suppress_warning {$\ = 'x'} + + begin + @io.print(5, 6, 7, 8) + @io.string.should == '5678xle' + ensure + suppress_warning {$\ = old_rs} + end + end + + it "updates the current position" do + @io.print(1, 2, 3) + @io.pos.should eql(3) + + @io.print(1, 2, 3) + @io.pos.should eql(6) + end + + it "correctly updates the current position when honoring the output record separator global" do + old_rs = $\ + suppress_warning {$\ = 'x'} + + begin + @io.print(5, 6, 7, 8) + @io.pos.should eql(5) + ensure + suppress_warning {$\ = old_rs} + end + end +end + +describe "StringIO#print when in append mode" do + before :each do + @io = StringIO.new(+"example", "a") + end + + it "appends the passed argument to the end of self" do + @io.print(", just testing") + @io.string.should == "example, just testing" + + @io.print(" and more testing") + @io.string.should == "example, just testing and more testing" + end + + it "correctly updates self's position" do + @io.print(", testing") + @io.pos.should eql(16) + end +end + +describe "StringIO#print when self is not writable" do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io.print("test") }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io.print("test") }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/printf_spec.rb b/spec/ruby/library/stringio/printf_spec.rb new file mode 100644 index 0000000000..ca82e84757 --- /dev/null +++ b/spec/ruby/library/stringio/printf_spec.rb @@ -0,0 +1,91 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../core/kernel/shared/sprintf' + +describe "StringIO#printf" do + before :each do + @io = StringIO.new() + end + + it "returns nil" do + @io.printf("%d %04x", 123, 123).should be_nil + end + + it "pads self with \\000 when the current position is after the end" do + @io.pos = 3 + @io.printf("%d", 123) + @io.string.should == "\000\000\000123" + end + + it "performs format conversion" do + @io.printf("%d %04x", 123, 123) + @io.string.should == "123 007b" + end + + it "updates the current position" do + @io.printf("%d %04x", 123, 123) + @io.pos.should eql(8) + + @io.printf("%d %04x", 123, 123) + @io.pos.should eql(16) + end + + describe "formatting" do + it_behaves_like :kernel_sprintf, -> format, *args { + io = StringIO.new(+"") + io.printf(format, *args) + io.string + } + end +end + +describe "StringIO#printf when in read-write mode" do + before :each do + @io = StringIO.new(+"example", "r+") + end + + it "starts from the beginning" do + @io.printf("%s", "abcdefghijk") + @io.string.should == "abcdefghijk" + end + + it "does not truncate existing string" do + @io.printf("%s", "abc") + @io.string.should == "abcmple" + end + + it "correctly updates self's position" do + @io.printf("%s", "abc") + @io.pos.should eql(3) + end +end + +describe "StringIO#printf when in append mode" do + before :each do + @io = StringIO.new(+"example", "a") + end + + it "appends the passed argument to the end of self" do + @io.printf("%d %04x", 123, 123) + @io.string.should == "example123 007b" + + @io.printf("%d %04x", 123, 123) + @io.string.should == "example123 007b123 007b" + end + + it "correctly updates self's position" do + @io.printf("%d %04x", 123, 123) + @io.pos.should eql(15) + end +end + +describe "StringIO#printf when self is not writable" do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io.printf("test") }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io.printf("test") }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/putc_spec.rb b/spec/ruby/library/stringio/putc_spec.rb new file mode 100644 index 0000000000..9f1ac8ffb2 --- /dev/null +++ b/spec/ruby/library/stringio/putc_spec.rb @@ -0,0 +1,103 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#putc when passed [String]" do + before :each do + @io = StringIO.new(+'example') + end + + it "overwrites the character at the current position" do + @io.putc("t") + @io.string.should == "txample" + + @io.pos = 3 + @io.putc("t") + @io.string.should == "txatple" + end + + it "only writes the first character from the passed String" do + @io.putc("test") + @io.string.should == "txample" + end + + it "returns the passed String" do + str = "test" + @io.putc(str).should equal(str) + end + + it "correctly updates the current position" do + @io.putc("t") + @io.pos.should == 1 + + @io.putc("test") + @io.pos.should == 2 + + @io.putc("t") + @io.pos.should == 3 + end + + it "handles concurrent writes correctly" do + @io = StringIO.new + n = 8 + go = false + threads = n.times.map { |i| + Thread.new { + Thread.pass until go + @io.putc i.to_s + } + } + go = true + threads.each(&:join) + @io.string.size.should == n + end +end + +describe "StringIO#putc when passed [Object]" do + before :each do + @io = StringIO.new(+'example') + end + + it "it writes the passed Integer % 256 to self" do + @io.putc(333) # 333 % 256 == ?M + @io.string.should == "Mxample" + + @io.putc(-450) # -450 % 256 == ?> + @io.string.should == "M>ample" + end + + it "pads self with \\000 when the current position is after the end" do + @io.pos = 10 + @io.putc(?A) + @io.string.should == "example\000\000\000A" + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(116) + @io.putc(obj) + @io.string.should == "txample" + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { @io.putc(Object.new) }.should raise_error(TypeError) + end +end + +describe "StringIO#putc when in append mode" do + it "appends to the end of self" do + io = StringIO.new(+"test", "a") + io.putc(?t) + io.string.should == "testt" + end +end + +describe "StringIO#putc when self is not writable" do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io.putc(?a) }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io.putc("t") }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/puts_spec.rb b/spec/ruby/library/stringio/puts_spec.rb new file mode 100644 index 0000000000..054ec8227f --- /dev/null +++ b/spec/ruby/library/stringio/puts_spec.rb @@ -0,0 +1,184 @@ +# -*- encoding: utf-8 -*- + +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#puts when passed an Array" do + before :each do + @io = StringIO.new + end + + it "writes each element of the passed Array to self, separated by a newline" do + @io.puts([1, 2, 3, 4]) + @io.string.should == "1\n2\n3\n4\n" + + @io.puts([1, 2], [3, 4]) + @io.string.should == "1\n2\n3\n4\n1\n2\n3\n4\n" + end + + it "flattens nested Arrays" do + @io.puts([1, [2, [3, [4]]]]) + @io.string.should == "1\n2\n3\n4\n" + end + + it "handles self-recursive arrays correctly" do + (ary = [5]) + ary << ary + @io.puts(ary) + @io.string.should == "5\n[...]\n" + end + + it "does not honor the global output record separator $\\" do + begin + old_rs = $\ + suppress_warning {$\ = "test"} + @io.puts([1, 2, 3, 4]) + @io.string.should == "1\n2\n3\n4\n" + ensure + suppress_warning {$\ = old_rs} + end + end + + it "first tries to convert each Array element to an Array using #to_ary" do + obj = mock("Object") + obj.should_receive(:to_ary).and_return(["to_ary"]) + @io.puts([obj]) + @io.string.should == "to_ary\n" + end + + it "then tries to convert each Array element to a String using #to_s" do + obj = mock("Object") + obj.should_receive(:to_s).and_return("to_s") + @io.puts([obj]) + @io.string.should == "to_s\n" + end + + it "returns general object info if :to_s does not return a string" do + object = mock('hola') + object.should_receive(:to_s).and_return(false) + + @io.puts(object).should == nil + @io.string.should == object.inspect.split(" ")[0] + ">\n" + end +end + +describe "StringIO#puts when passed 1 or more objects" do + before :each do + @io = StringIO.new + end + + it "does not honor the global output record separator $\\" do + begin + old_rs = $\ + suppress_warning {$\ = "test"} + @io.puts(1, 2, 3, 4) + @io.string.should == "1\n2\n3\n4\n" + ensure + suppress_warning {$\ = old_rs} + end + end + + it "does not put a \\n after each Objects that end in a newline" do + @io.puts("1\n", "2\n", "3\n") + @io.string.should == "1\n2\n3\n" + end + + it "first tries to convert each Object to an Array using #to_ary" do + obj = mock("Object") + obj.should_receive(:to_ary).and_return(["to_ary"]) + @io.puts(obj) + @io.string.should == "to_ary\n" + end + + it "then tries to convert each Object to a String using #to_s" do + obj = mock("Object") + obj.should_receive(:to_s).and_return("to_s") + @io.puts(obj) + @io.string.should == "to_s\n" + end + + it "prints a newline when passed an empty string" do + @io.puts '' + @io.string.should == "\n" + end + + it "handles concurrent writes correctly" do + n = 8 + go = false + threads = n.times.map { |i| + Thread.new { + Thread.pass until go + @io.puts i + } + } + go = true + threads.each(&:join) + @io.string.size.should == n.times.map { |i| "#{i}\n" }.join.size + end +end + +describe "StringIO#puts when passed no arguments" do + before :each do + @io = StringIO.new + end + + it "returns nil" do + @io.puts.should be_nil + end + + it "prints a newline" do + @io.puts + @io.string.should == "\n" + end + + it "does not honor the global output record separator $\\" do + begin + old_rs = $\ + suppress_warning {$\ = "test"} + @io.puts + @io.string.should == "\n" + ensure + suppress_warning {$\ = old_rs} + end + end +end + +describe "StringIO#puts when in append mode" do + before :each do + @io = StringIO.new(+"example", "a") + end + + it "appends the passed argument to the end of self" do + @io.puts(", just testing") + @io.string.should == "example, just testing\n" + + @io.puts(" and more testing") + @io.string.should == "example, just testing\n and more testing\n" + end + + it "correctly updates self's position" do + @io.puts(", testing") + @io.pos.should eql(17) + end +end + +describe "StringIO#puts when self is not writable" do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io.puts }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io.puts }.should raise_error(IOError) + end +end + +describe "StringIO#puts when passed an encoded string" do + it "stores the bytes unmodified" do + io = StringIO.new(+"") + io.puts "\x00\x01\x02" + io.puts "æåø" + + io.string.should == "\x00\x01\x02\næåø\n" + end +end diff --git a/spec/ruby/library/stringio/read_nonblock_spec.rb b/spec/ruby/library/stringio/read_nonblock_spec.rb new file mode 100644 index 0000000000..74736f7792 --- /dev/null +++ b/spec/ruby/library/stringio/read_nonblock_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require "stringio" +require_relative 'shared/read' +require_relative 'shared/sysread' + +describe "StringIO#read_nonblock when passed length, buffer" do + it_behaves_like :stringio_read, :read_nonblock + + it "accepts :exception option" do + io = StringIO.new("example") + io.read_nonblock(3, buffer = +"", exception: true) + buffer.should == "exa" + end +end + +describe "StringIO#read_nonblock when passed length" do + it_behaves_like :stringio_read_length, :read_nonblock + + it "accepts :exception option" do + io = StringIO.new("example") + io.read_nonblock(3, exception: true).should == "exa" + end +end + +describe "StringIO#read_nonblock when passed nil" do + it_behaves_like :stringio_read_nil, :read_nonblock +end + +describe "StringIO#read_nonblock when passed length" do + it_behaves_like :stringio_sysread_length, :read_nonblock +end + +describe "StringIO#read_nonblock" do + + it "accepts an exception option" do + stringio = StringIO.new('foo') + stringio.read_nonblock(3, exception: false).should == 'foo' + end + + context "when exception option is set to false" do + context "when the end is reached" do + it "returns nil" do + stringio = StringIO.new(+'') + stringio << "hello" + stringio.rewind + + stringio.read_nonblock(5).should == "hello" + stringio.read_nonblock(5, exception: false).should be_nil + end + end + end + +end diff --git a/spec/ruby/library/stringio/read_spec.rb b/spec/ruby/library/stringio/read_spec.rb new file mode 100644 index 0000000000..e49f262127 --- /dev/null +++ b/spec/ruby/library/stringio/read_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require "stringio" +require_relative 'shared/read' + +describe "StringIO#read when passed length, buffer" do + it_behaves_like :stringio_read, :read +end + +describe "StringIO#read when passed [length]" do + it_behaves_like :stringio_read_length, :read +end + +describe "StringIO#read when passed no arguments" do + it_behaves_like :stringio_read_no_arguments, :read + + it "returns an empty string if at EOF" do + @io.read.should == "example" + @io.read.should == "" + end +end + +describe "StringIO#read when passed nil" do + it_behaves_like :stringio_read_nil, :read + + it "returns an empty string if at EOF" do + @io.read(nil).should == "example" + @io.read(nil).should == "" + end +end + +describe "StringIO#read when self is not readable" do + it_behaves_like :stringio_read_not_readable, :read +end + +describe "StringIO#read when passed [length]" do + before :each do + @io = StringIO.new("example") + end + + it "returns nil when self's position is at the end" do + @io.pos = 7 + @io.read(10).should be_nil + end + + it "returns an empty String when length is 0" do + @io.read(0).should == "" + end +end + +describe "StringIO#read when passed length and a buffer" do + before :each do + @io = StringIO.new("abcdefghijklmnopqrstuvwxyz") + end + + it "reads [length] characters into the buffer" do + buf = +"foo" + result = @io.read(10, buf) + + buf.should == "abcdefghij" + result.should equal(buf) + end +end diff --git a/spec/ruby/library/stringio/readbyte_spec.rb b/spec/ruby/library/stringio/readbyte_spec.rb new file mode 100644 index 0000000000..41a0911293 --- /dev/null +++ b/spec/ruby/library/stringio/readbyte_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/readchar' + +describe "StringIO#readbyte" do + it_behaves_like :stringio_readchar, :readbyte + + it "reads the next 8-bit byte from self's current position" do + io = StringIO.new("example") + + io.readbyte.should == 101 + + io.pos = 4 + io.readbyte.should == 112 + end +end + +describe "StringIO#readbyte when self is not readable" do + it_behaves_like :stringio_readchar_not_readable, :readbyte +end diff --git a/spec/ruby/library/stringio/readchar_spec.rb b/spec/ruby/library/stringio/readchar_spec.rb new file mode 100644 index 0000000000..38944819a2 --- /dev/null +++ b/spec/ruby/library/stringio/readchar_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'stringio' +require_relative 'shared/readchar' + +describe "StringIO#readchar" do + it_behaves_like :stringio_readchar, :readchar + + it "reads the next 8-bit byte from self's current position" do + io = StringIO.new("example") + + io.readchar.should == ?e + + io.pos = 4 + io.readchar.should == ?p + end +end + +describe "StringIO#readchar when self is not readable" do + it_behaves_like :stringio_readchar_not_readable, :readchar +end diff --git a/spec/ruby/library/stringio/readline_spec.rb b/spec/ruby/library/stringio/readline_spec.rb new file mode 100644 index 0000000000..085360707f --- /dev/null +++ b/spec/ruby/library/stringio/readline_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' +require "stringio" +require_relative 'fixtures/classes' +require_relative "shared/gets" + +describe "StringIO#readline" do + describe "when passed [separator]" do + it_behaves_like :stringio_gets_separator, :readline + + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + -> { @io.readline(">") }.should raise_error(IOError) + end + end + + describe "when passed [limit]" do + it_behaves_like :stringio_gets_limit, :readline + + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + -> { @io.readline(3) }.should raise_error(IOError) + end + end + + describe "when passed [separator] and [limit]" do + it_behaves_like :stringio_gets_separator_and_limit, :readline + + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + -> { @io.readline(">", 3) }.should raise_error(IOError) + end + end + + describe "when passed no argument" do + it_behaves_like :stringio_gets_no_argument, :readline + + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + -> { @io.readline }.should raise_error(IOError) + end + end + + describe "when passed [chomp]" do + it_behaves_like :stringio_gets_chomp, :readline + end + + describe "when in write-only mode" do + it_behaves_like :stringio_gets_write_only, :readline + end +end diff --git a/spec/ruby/library/stringio/readlines_spec.rb b/spec/ruby/library/stringio/readlines_spec.rb new file mode 100644 index 0000000000..ed7cc22b3d --- /dev/null +++ b/spec/ruby/library/stringio/readlines_spec.rb @@ -0,0 +1,118 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#readlines when passed [separator]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns an Array containing lines based on the passed separator" do + @io.readlines(">").should == ["this>", "is>", "an>", "example"] + end + + it "updates self's position based on the number of read bytes" do + @io.readlines(">") + @io.pos.should eql(18) + end + + it "updates self's lineno based on the number of read lines" do + @io.readlines(">") + @io.lineno.should eql(4) + end + + it "does not change $_" do + $_ = "test" + @io.readlines(">") + $_.should == "test" + end + + it "returns an Array containing all paragraphs when the passed separator is an empty String" do + io = StringIO.new("this is\n\nan example") + io.readlines("").should == ["this is\n\n", "an example"] + end + + it "returns the remaining content as one line starting at the current position when passed nil" do + io = StringIO.new("this is\n\nan example") + io.pos = 5 + io.readlines(nil).should == ["is\n\nan example"] + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock('to_str') + obj.stub!(:to_str).and_return(">") + @io.readlines(obj).should == ["this>", "is>", "an>", "example"] + end +end + +describe "StringIO#readlines when passed no argument" do + before :each do + @io = StringIO.new("this is\nan example\nfor StringIO#readlines") + end + + it "returns an Array containing lines based on $/" do + begin + old_sep = $/; + suppress_warning {$/ = " "} + @io.readlines.should == ["this ", "is\nan ", "example\nfor ", "StringIO#readlines"] + ensure + suppress_warning {$/ = old_sep} + end + end + + it "updates self's position based on the number of read bytes" do + @io.readlines + @io.pos.should eql(41) + end + + it "updates self's lineno based on the number of read lines" do + @io.readlines + @io.lineno.should eql(3) + end + + it "does not change $_" do + $_ = "test" + @io.readlines(">") + $_.should == "test" + end + + it "returns an empty Array when self is at the end" do + @io.pos = 41 + @io.readlines.should == [] + end +end + +describe "StringIO#readlines when in write-only mode" do + it "raises an IOError" do + io = StringIO.new(+"xyz", "w") + -> { io.readlines }.should raise_error(IOError) + + io = StringIO.new("xyz") + io.close_read + -> { io.readlines }.should raise_error(IOError) + end +end + +describe "StringIO#readlines when passed [chomp]" do + it "returns the data read without a trailing newline character" do + io = StringIO.new("this>is\nan>example\r\n") + io.readlines(chomp: true).should == ["this>is", "an>example"] + end +end + +describe "StringIO#readlines when passed [limit]" do + before :each do + @io = StringIO.new("a b c d e\n1 2 3 4 5") + end + + it "returns the data read until the limit is met" do + @io.readlines(4).should == ["a b ", "c d ", "e\n", "1 2 ", "3 4 ", "5"] + end + + it "raises ArgumentError when limit is 0" do + -> { @io.readlines(0) }.should raise_error(ArgumentError) + end + + it "ignores it when the limit is negative" do + @io.readlines(-4).should == ["a b c d e\n", "1 2 3 4 5"] + end +end diff --git a/spec/ruby/library/stringio/readpartial_spec.rb b/spec/ruby/library/stringio/readpartial_spec.rb new file mode 100644 index 0000000000..dadefb7837 --- /dev/null +++ b/spec/ruby/library/stringio/readpartial_spec.rb @@ -0,0 +1,102 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#readpartial" do + before :each do + @string = StringIO.new(+'Stop, look, listen') + end + + after :each do + @string.close unless @string.closed? + end + + it "reads at most the specified number of bytes" do + # buffered read + @string.read(1).should == 'S' + # return only specified number, not the whole buffer + @string.readpartial(1).should == "t" + end + + it "reads after ungetc with data in the buffer" do + c = @string.getc + @string.ungetc(c) + @string.readpartial(4).should == "Stop" + @string.readpartial(3).should == ", l" + end + + it "reads after ungetc with multibyte characters in the buffer" do + @string = StringIO.new(+"∂φ/∂x = gaîté") + c = @string.getc + @string.ungetc(c) + @string.readpartial(3).should == "\xE2\x88\x82".b + @string.readpartial(3).should == "\xCF\x86/".b + end + + it "reads after ungetc without data in the buffer" do + @string = StringIO.new + @string.write("f").should == 1 + @string.rewind + c = @string.getc + c.should == 'f' + @string.ungetc(c).should == nil + + @string.readpartial(2).should == "f" + @string.rewind + # now, also check that the ungot char is cleared and + # not returned again + @string.write("b").should == 1 + @string.rewind + @string.readpartial(2).should == "b" + end + + it "discards the existing buffer content upon successful read" do + buffer = +"existing" + @string.readpartial(11, buffer) + buffer.should == "Stop, look," + end + + it "raises EOFError on EOF" do + @string.readpartial(18).should == 'Stop, look, listen' + -> { @string.readpartial(10) }.should raise_error(EOFError) + end + + it "discards the existing buffer content upon error" do + buffer = +'hello' + @string.readpartial(100) + -> { @string.readpartial(1, buffer) }.should raise_error(EOFError) + buffer.should be_empty + end + + it "raises IOError if the stream is closed" do + @string.close + -> { @string.readpartial(1) }.should raise_error(IOError, "not opened for reading") + end + + it "raises ArgumentError if the negative argument is provided" do + -> { @string.readpartial(-1) }.should raise_error(ArgumentError, "negative length -1 given") + end + + it "immediately returns an empty string if the length argument is 0" do + @string.readpartial(0).should == "" + end + + it "raises IOError if the stream is closed and the length argument is 0" do + @string.close + -> { @string.readpartial(0) }.should raise_error(IOError, "not opened for reading") + end + + it "clears and returns the given buffer if the length argument is 0" do + buffer = +"existing content" + @string.readpartial(0, buffer).should == buffer + buffer.should == "" + end + + version_is StringIO::VERSION, "3.1.2" do # ruby_version_is "3.4" + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @string.readpartial(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + end +end diff --git a/spec/ruby/library/stringio/reopen_spec.rb b/spec/ruby/library/stringio/reopen_spec.rb new file mode 100644 index 0000000000..7021ff17e5 --- /dev/null +++ b/spec/ruby/library/stringio/reopen_spec.rb @@ -0,0 +1,251 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#reopen when passed [Object, Integer]" do + before :each do + @io = StringIO.new("example") + end + + it "reopens self with the passed Object in the passed mode" do + @io.reopen("reopened", IO::RDONLY) + @io.closed_read?.should be_false + @io.closed_write?.should be_true + @io.string.should == "reopened" + + @io.reopen(+"reopened, twice", IO::WRONLY) + @io.closed_read?.should be_true + @io.closed_write?.should be_false + @io.string.should == "reopened, twice" + + @io.reopen(+"reopened, another time", IO::RDWR) + @io.closed_read?.should be_false + @io.closed_write?.should be_false + @io.string.should == "reopened, another time" + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(+"to_str") + @io.reopen(obj, IO::RDWR) + @io.string.should == "to_str" + end + + it "raises a TypeError when the passed Object can't be converted to a String" do + -> { @io.reopen(Object.new, IO::RDWR) }.should raise_error(TypeError) + end + + it "raises an Errno::EACCES when trying to reopen self with a frozen String in write-mode" do + -> { @io.reopen("burn".freeze, IO::WRONLY) }.should raise_error(Errno::EACCES) + -> { @io.reopen("burn".freeze, IO::WRONLY | IO::APPEND) }.should raise_error(Errno::EACCES) + end + + it "raises a FrozenError when trying to reopen self with a frozen String in truncate-mode" do + -> { @io.reopen("burn".freeze, IO::RDONLY | IO::TRUNC) }.should raise_error(FrozenError) + end + + it "does not raise IOError when passed a frozen String in read-mode" do + @io.reopen("burn".freeze, IO::RDONLY) + @io.string.should == "burn" + end +end + +describe "StringIO#reopen when passed [Object, Object]" do + before :each do + @io = StringIO.new("example") + end + + it "reopens self with the passed Object in the passed mode" do + @io.reopen("reopened", "r") + @io.closed_read?.should be_false + @io.closed_write?.should be_true + @io.string.should == "reopened" + + @io.reopen(+"reopened, twice", "r+") + @io.closed_read?.should be_false + @io.closed_write?.should be_false + @io.string.should == "reopened, twice" + + @io.reopen(+"reopened, another", "w+") + @io.closed_read?.should be_false + @io.closed_write?.should be_false + @io.string.should == "" + + @io.reopen(+"reopened, another time", "r+") + @io.closed_read?.should be_false + @io.closed_write?.should be_false + @io.string.should == "reopened, another time" + end + + it "truncates the passed String when opened in truncate mode" do + @io.reopen(str = +"reopened", "w") + str.should == "" + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return("to_str") + @io.reopen(obj, "r") + @io.string.should == "to_str" + end + + it "raises a TypeError when the passed Object can't be converted to a String using #to_str" do + -> { @io.reopen(Object.new, "r") }.should raise_error(TypeError) + end + + it "resets self's position to 0" do + @io.read(5) + @io.reopen(+"reopened") + @io.pos.should eql(0) + end + + it "resets self's line number to 0" do + @io.gets + @io.reopen(+"reopened") + @io.lineno.should eql(0) + end + + it "tries to convert the passed mode Object to an Integer using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return("r") + @io.reopen("reopened", obj) + @io.closed_read?.should be_false + @io.closed_write?.should be_true + @io.string.should == "reopened" + end + + it "raises an Errno::EACCES error when trying to reopen self with a frozen String in write-mode" do + -> { @io.reopen("burn".freeze, 'w') }.should raise_error(Errno::EACCES) + -> { @io.reopen("burn".freeze, 'w+') }.should raise_error(Errno::EACCES) + -> { @io.reopen("burn".freeze, 'a') }.should raise_error(Errno::EACCES) + -> { @io.reopen("burn".freeze, "r+") }.should raise_error(Errno::EACCES) + end + + it "does not raise IOError if a frozen string is passed in read mode" do + @io.reopen("burn".freeze, "r") + @io.string.should == "burn" + end +end + +describe "StringIO#reopen when passed [String]" do + before :each do + @io = StringIO.new("example") + end + + it "reopens self with the passed String in read-write mode" do + @io.close + + @io.reopen(+"reopened") + + @io.closed_write?.should be_false + @io.closed_read?.should be_false + + @io.string.should == "reopened" + end + + it "resets self's position to 0" do + @io.read(5) + @io.reopen(+"reopened") + @io.pos.should eql(0) + end + + it "resets self's line number to 0" do + @io.gets + @io.reopen(+"reopened") + @io.lineno.should eql(0) + end +end + +describe "StringIO#reopen when passed [Object]" do + before :each do + @io = StringIO.new("example") + end + + it "raises a TypeError when passed an Object that can't be converted to a StringIO" do + -> { @io.reopen(Object.new) }.should raise_error(TypeError) + end + + it "does not try to convert the passed Object to a String using #to_str" do + obj = mock("not to_str") + obj.should_not_receive(:to_str) + -> { @io.reopen(obj) }.should raise_error(TypeError) + end + + it "tries to convert the passed Object to a StringIO using #to_strio" do + obj = mock("to_strio") + obj.should_receive(:to_strio).and_return(StringIO.new(+"to_strio")) + @io.reopen(obj) + @io.string.should == "to_strio" + end +end + +describe "StringIO#reopen when passed no arguments" do + before :each do + @io = StringIO.new("example\nsecond line") + end + + it "resets self's mode to read-write" do + @io.close + @io.reopen + @io.closed_read?.should be_false + @io.closed_write?.should be_false + end + + it "resets self's position to 0" do + @io.read(5) + @io.reopen + @io.pos.should eql(0) + end + + it "resets self's line number to 0" do + @io.gets + @io.reopen + @io.lineno.should eql(0) + end +end + +# NOTE: Some reopen specs disabled due to MRI bugs. See: +# http://rubyforge.org/tracker/index.php?func=detail&aid=13919&group_id=426&atid=1698 +# for details. +describe "StringIO#reopen" do + before :each do + @io = StringIO.new(+'hello', 'a') + end + + # TODO: find out if this is really a bug + it "reopens a stream when given a String argument" do + @io.reopen(+'goodbye').should == @io + @io.string.should == 'goodbye' + @io << 'x' + @io.string.should == 'xoodbye' + end + + it "reopens a stream in append mode when flagged as such" do + @io.reopen(+'goodbye', 'a').should == @io + @io.string.should == 'goodbye' + @io << 'x' + @io.string.should == 'goodbyex' + end + + it "reopens and truncate when reopened in write mode" do + @io.reopen(+'goodbye', 'wb').should == @io + @io.string.should == '' + @io << 'x' + @io.string.should == 'x' + end + + it "truncates the given string, not a copy" do + str = +'goodbye' + @io.reopen(str, 'w') + @io.string.should == '' + str.should == '' + end + + it "does not truncate the content even when the StringIO argument is in the truncate mode" do + orig_io = StringIO.new(+"Original StringIO", IO::RDWR|IO::TRUNC) + orig_io.write("BLAH") # make sure the content is not empty + + @io.reopen(orig_io) + @io.string.should == "BLAH" + end + +end diff --git a/spec/ruby/library/stringio/rewind_spec.rb b/spec/ruby/library/stringio/rewind_spec.rb new file mode 100644 index 0000000000..5f885ecf81 --- /dev/null +++ b/spec/ruby/library/stringio/rewind_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#rewind" do + before :each do + @io = StringIO.new("hello\nworld") + @io.pos = 3 + @io.lineno = 1 + end + + it "returns 0" do + @io.rewind.should eql(0) + end + + it "resets the position" do + @io.rewind + @io.pos.should == 0 + end + + it "resets the line number" do + @io.rewind + @io.lineno.should == 0 + end +end diff --git a/spec/ruby/library/stringio/seek_spec.rb b/spec/ruby/library/stringio/seek_spec.rb new file mode 100644 index 0000000000..253b5027a9 --- /dev/null +++ b/spec/ruby/library/stringio/seek_spec.rb @@ -0,0 +1,67 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#seek" do + before :each do + @io = StringIO.new("12345678") + end + + it "seeks from the current position when whence is IO::SEEK_CUR" do + @io.pos = 1 + @io.seek(1, IO::SEEK_CUR) + @io.pos.should eql(2) + + @io.seek(-1, IO::SEEK_CUR) + @io.pos.should eql(1) + end + + it "seeks from the end of self when whence is IO::SEEK_END" do + @io.seek(3, IO::SEEK_END) + @io.pos.should eql(11) # Outside of the StringIO's content + + @io.seek(-2, IO::SEEK_END) + @io.pos.should eql(6) + end + + it "seeks to an absolute position when whence is IO::SEEK_SET" do + @io.seek(5, IO::SEEK_SET) + @io.pos.should == 5 + + @io.pos = 3 + @io.seek(5, IO::SEEK_SET) + @io.pos.should == 5 + end + + it "raises an Errno::EINVAL error on negative amounts when whence is IO::SEEK_SET" do + -> { @io.seek(-5, IO::SEEK_SET) }.should raise_error(Errno::EINVAL) + end + + it "raises an Errno::EINVAL error on incorrect whence argument" do + -> { @io.seek(0, 3) }.should raise_error(Errno::EINVAL) + -> { @io.seek(0, -1) }.should raise_error(Errno::EINVAL) + -> { @io.seek(0, 2**16) }.should raise_error(Errno::EINVAL) + -> { @io.seek(0, -2**16) }.should raise_error(Errno::EINVAL) + end + + it "tries to convert the passed Object to a String using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(2) + @io.seek(obj) + @io.pos.should eql(2) + end + + it "raises a TypeError when the passed Object can't be converted to an Integer" do + -> { @io.seek(Object.new) }.should raise_error(TypeError) + end +end + +describe "StringIO#seek when self is closed" do + before :each do + @io = StringIO.new("example") + @io.close + end + + it "raises an IOError" do + -> { @io.seek(5) }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/set_encoding_by_bom_spec.rb b/spec/ruby/library/stringio/set_encoding_by_bom_spec.rb new file mode 100644 index 0000000000..1030aad042 --- /dev/null +++ b/spec/ruby/library/stringio/set_encoding_by_bom_spec.rb @@ -0,0 +1,237 @@ +require 'stringio' +require_relative '../../spec_helper' + +# Should be synced with specs for IO#set_encoding_by_bom +describe "StringIO#set_encoding_by_bom" do + it "returns nil if not readable" do + io = StringIO.new("".b, "wb") + + io.set_encoding_by_bom.should be_nil + io.external_encoding.should == Encoding::ASCII_8BIT + end + + it "returns the result encoding if found BOM UTF-8 sequence" do + io = StringIO.new("\u{FEFF}".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_8 + io.external_encoding.should == Encoding::UTF_8 + io.read.b.should == "".b + + io = StringIO.new("\u{FEFF}abc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_8 + io.external_encoding.should == Encoding::UTF_8 + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_16LE sequence" do + io = StringIO.new("\xFF\xFE".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "".b + + io = StringIO.new("\xFF\xFEabc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_16BE sequence" do + io = StringIO.new("\xFE\xFF".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16BE + io.external_encoding.should == Encoding::UTF_16BE + io.read.b.should == "".b + + io = StringIO.new("\xFE\xFFabc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16BE + io.external_encoding.should == Encoding::UTF_16BE + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_32LE sequence" do + io = StringIO.new("\xFF\xFE\x00\x00".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32LE + io.external_encoding.should == Encoding::UTF_32LE + io.read.b.should == "".b + + io = StringIO.new("\xFF\xFE\x00\x00abc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32LE + io.external_encoding.should == Encoding::UTF_32LE + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_32BE sequence" do + io = StringIO.new("\x00\x00\xFE\xFF".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32BE + io.external_encoding.should == Encoding::UTF_32BE + io.read.b.should == "".b + + io = StringIO.new("\x00\x00\xFE\xFFabc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32BE + io.external_encoding.should == Encoding::UTF_32BE + io.read.b.should == "abc".b + end + + it "returns nil if io is empty" do + io = StringIO.new("".b, "rb") + io.set_encoding_by_bom.should be_nil + io.external_encoding.should == Encoding::ASCII_8BIT + end + + it "returns nil if UTF-8 BOM sequence is incomplete" do + io = StringIO.new("\xEF".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEF".b + + io = StringIO.new("\xEFa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEFa".b + + io = StringIO.new("\xEF\xBB".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEF\xBB".b + + io = StringIO.new("\xEF\xBBa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEF\xBBa".b + end + + it "returns nil if UTF-16BE BOM sequence is incomplete" do + io = StringIO.new("\xFE".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFE".b + + io = StringIO.new("\xFEa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFEa".b + end + + it "returns nil if UTF-16LE/UTF-32LE BOM sequence is incomplete" do + io = StringIO.new("\xFF".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFF".b + + io = StringIO.new("\xFFa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFFa".b + end + + it "returns UTF-16LE if UTF-32LE BOM sequence is incomplete" do + io = StringIO.new("\xFF\xFE".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "".b + + io = StringIO.new("\xFF\xFE\x00".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "\x00".b + + io = StringIO.new("\xFF\xFE\x00a".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "\x00a".b + end + + it "returns nil if UTF-32BE BOM sequence is incomplete" do + io = StringIO.new("\x00".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00".b + + io = StringIO.new("\x00a".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00a".b + + io = StringIO.new("\x00\x00".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00".b + + io = StringIO.new("\x00\x00a".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00a".b + + io = StringIO.new("\x00\x00\xFE".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00\xFE".b + + io = StringIO.new("\x00\x00\xFEa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00\xFEa".b + end + + it "returns nil if found BOM sequence not provided" do + io = StringIO.new("abc".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read(3).should == "abc".b + end + + it "does not raise exception if io not in binary mode" do + io = StringIO.new("", 'r') + io.set_encoding_by_bom.should == nil + end + + it "does not raise exception if encoding already set" do + io = StringIO.new("".b, "rb") + io.set_encoding("utf-8") + io.set_encoding_by_bom.should == nil + end + + it "does not raise exception if encoding conversion is already set" do + io = StringIO.new("".b, "rb") + io.set_encoding(Encoding::UTF_8, Encoding::UTF_16BE) + + io.set_encoding_by_bom.should == nil + end + + it "raises FrozenError when io is frozen" do + io = StringIO.new() + io.freeze + -> { io.set_encoding_by_bom }.should raise_error(FrozenError) + end + + it "does not raise FrozenError when initial string is frozen" do + io = StringIO.new("".freeze) + io.set_encoding_by_bom.should == nil + end +end diff --git a/spec/ruby/library/stringio/set_encoding_spec.rb b/spec/ruby/library/stringio/set_encoding_spec.rb new file mode 100644 index 0000000000..19ca0875bf --- /dev/null +++ b/spec/ruby/library/stringio/set_encoding_spec.rb @@ -0,0 +1,28 @@ +require 'stringio' +require_relative '../../spec_helper' + +describe "StringIO#set_encoding" do + it "sets the encoding of the underlying String if the String is not frozen" do + str = "".encode(Encoding::US_ASCII) + + io = StringIO.new(str) + io.set_encoding Encoding::UTF_8 + io.string.encoding.should == Encoding::UTF_8 + end + + it "does not set the encoding of the underlying String if the String is frozen" do + str = "".encode(Encoding::US_ASCII).freeze + + io = StringIO.new(str) + io.set_encoding Encoding::UTF_8 + io.string.encoding.should == Encoding::US_ASCII + end + + it "accepts a String" do + str = "".encode(Encoding::US_ASCII) + io = StringIO.new(str) + io.set_encoding("ASCII-8BIT") + io.external_encoding.should == Encoding::BINARY + str.encoding.should == Encoding::BINARY + end +end diff --git a/spec/ruby/library/stringio/shared/codepoints.rb b/spec/ruby/library/stringio/shared/codepoints.rb new file mode 100644 index 0000000000..25333bb0fd --- /dev/null +++ b/spec/ruby/library/stringio/shared/codepoints.rb @@ -0,0 +1,45 @@ +# -*- encoding: utf-8 -*- +describe :stringio_codepoints, shared: true do + before :each do + @io = StringIO.new("∂φ/∂x = gaîté") + @enum = @io.send(@method) + end + + it "returns an Enumerator" do + @enum.should be_an_instance_of(Enumerator) + end + + it "yields each codepoint code in turn" do + @enum.to_a.should == [8706, 966, 47, 8706, 120, 32, 61, 32, 103, 97, 238, 116, 233] + end + + it "yields each codepoint starting from the current position" do + @io.pos = 15 + @enum.to_a.should == [238, 116, 233] + end + + it "raises an error if reading invalid sequence" do + @io.pos = 1 # inside of a multibyte sequence + -> { @enum.first }.should raise_error(ArgumentError) + end + + it "raises an IOError if not readable" do + @io.close_read + -> { @enum.to_a }.should raise_error(IOError) + + io = StringIO.new(+"xyz", "w") + -> { io.send(@method).to_a }.should raise_error(IOError) + end + + + it "calls the given block" do + r = [] + @io.send(@method){|c| r << c } + r.should == [8706, 966, 47, 8706, 120, 32, 61, 32, 103, 97, 238, 116, 233] + end + + it "returns self" do + @io.send(@method) {|l| l }.should equal(@io) + end + +end diff --git a/spec/ruby/library/stringio/shared/each.rb b/spec/ruby/library/stringio/shared/each.rb new file mode 100644 index 0000000000..626b41a4d3 --- /dev/null +++ b/spec/ruby/library/stringio/shared/each.rb @@ -0,0 +1,209 @@ +describe :stringio_each_separator, shared: true do + before :each do + @io = StringIO.new("a b c d e\n1 2 3 4 5") + end + + it "uses the passed argument as the line separator" do + seen = [] + @io.send(@method, " ") {|s| seen << s} + seen.should == ["a ", "b ", "c ", "d ", "e\n1 ", "2 ", "3 ", "4 ", "5"] + end + + it "does not change $_" do + $_ = "test" + @io.send(@method, " ") { |s| s} + $_.should == "test" + end + + it "returns self" do + @io.send(@method) {|l| l }.should equal(@io) + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock("to_str") + obj.stub!(:to_str).and_return(" ") + + seen = [] + @io.send(@method, obj) { |l| seen << l } + seen.should == ["a ", "b ", "c ", "d ", "e\n1 ", "2 ", "3 ", "4 ", "5"] + end + + it "yields self's content starting from the current position when the passed separator is nil" do + seen = [] + io = StringIO.new("1 2 1 2 1 2") + io.pos = 2 + io.send(@method, nil) {|s| seen << s} + seen.should == ["2 1 2 1 2"] + end + + it "yields each paragraph with all separation characters when passed an empty String as separator" do + seen = [] + io = StringIO.new("para1\n\npara2\n\n\npara3") + io.send(@method, "") {|s| seen << s} + seen.should == ["para1\n\n", "para2\n\n\n", "para3"] + end +end + +describe :stringio_each_no_arguments, shared: true do + before :each do + @io = StringIO.new("a b c d e\n1 2 3 4 5") + end + + it "yields each line to the passed block" do + seen = [] + @io.send(@method) {|s| seen << s } + seen.should == ["a b c d e\n", "1 2 3 4 5"] + end + + it "yields each line starting from the current position" do + seen = [] + @io.pos = 4 + @io.send(@method) {|s| seen << s } + seen.should == ["c d e\n", "1 2 3 4 5"] + end + + it "does not change $_" do + $_ = "test" + @io.send(@method) { |s| s} + $_.should == "test" + end + + it "uses $/ as the default line separator" do + seen = [] + begin + old_rs = $/ + suppress_warning {$/ = " "} + @io.send(@method) {|s| seen << s } + seen.should eql(["a ", "b ", "c ", "d ", "e\n1 ", "2 ", "3 ", "4 ", "5"]) + ensure + suppress_warning {$/ = old_rs} + end + end + + it "returns self" do + @io.send(@method) {|l| l }.should equal(@io) + end + + it "returns an Enumerator when passed no block" do + enum = @io.send(@method) + enum.instance_of?(Enumerator).should be_true + + seen = [] + enum.each { |b| seen << b } + seen.should == ["a b c d e\n", "1 2 3 4 5"] + end +end + +describe :stringio_each_not_readable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"a b c d e", "w") + -> { io.send(@method) { |b| b } }.should raise_error(IOError) + + io = StringIO.new("a b c d e") + io.close_read + -> { io.send(@method) { |b| b } }.should raise_error(IOError) + end +end + +describe :stringio_each_chomp, shared: true do + it "yields each line with removed newline characters to the passed block" do + seen = [] + io = StringIO.new("a b \rc d e\n1 2 3 4 5\r\nthe end") + io.send(@method, chomp: true) {|s| seen << s } + seen.should == ["a b \rc d e", "1 2 3 4 5", "the end"] + end + + it "returns each line with removed newline characters when called without block" do + seen = [] + io = StringIO.new("a b \rc d e\n1 2 3 4 5\r\nthe end") + enum = io.send(@method, chomp: true) + enum.each {|s| seen << s } + seen.should == ["a b \rc d e", "1 2 3 4 5", "the end"] + end +end + +describe :stringio_each_separator_and_chomp, shared: true do + it "yields each line with removed separator to the passed block" do + seen = [] + io = StringIO.new("a b \nc d e|1 2 3 4 5\n|the end") + io.send(@method, "|", chomp: true) {|s| seen << s } + seen.should == ["a b \nc d e", "1 2 3 4 5\n", "the end"] + end + + it "returns each line with removed separator when called without block" do + seen = [] + io = StringIO.new("a b \nc d e|1 2 3 4 5\n|the end") + enum = io.send(@method, "|", chomp: true) + enum.each {|s| seen << s } + seen.should == ["a b \nc d e", "1 2 3 4 5\n", "the end"] + end +end + +describe :stringio_each_limit, shared: true do + before :each do + @io = StringIO.new("a b c d e\n1 2 3 4 5") + end + + it "returns the data read until the limit is met" do + seen = [] + @io.send(@method, 4) { |s| seen << s } + seen.should == ["a b ", "c d ", "e\n", "1 2 ", "3 4 ", "5"] + end +end + +describe :stringio_each_separator_and_limit, shared: true do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read until the limit is consumed or the separator is met" do + @io.send(@method, '>', 8) { |s| break s }.should == "this>" + @io.send(@method, '>', 2) { |s| break s }.should == "is" + @io.send(@method, '>', 10) { |s| break s }.should == ">" + @io.send(@method, '>', 6) { |s| break s }.should == "an>" + @io.send(@method, '>', 5) { |s| break s }.should == "examp" + end + + it "truncates the multi-character separator at the end to meet the limit" do + @io.send(@method, "is>an", 7) { |s| break s }.should == "this>is" + end + + it "does not change $_" do + $_ = "test" + @io.send(@method, '>', 8) { |s| s } + $_.should == "test" + end + + it "updates self's lineno by one" do + @io.send(@method, '>', 3) { |s| break s } + @io.lineno.should eql(1) + + @io.send(@method, '>', 3) { |s| break s } + @io.lineno.should eql(2) + + @io.send(@method, '>', 3) { |s| break s } + @io.lineno.should eql(3) + end + + it "tries to convert the passed separator to a String using #to_str" do # TODO + obj = mock('to_str') + obj.should_receive(:to_str).and_return('>') + + seen = [] + @io.send(@method, obj, 5) { |s| seen << s } + seen.should == ["this>", "is>", "an>", "examp", "le"] + end + + it "does not raise TypeError if passed separator is nil" do + @io.send(@method, nil, 5) { |s| break s }.should == "this>" + end + + it "tries to convert the passed limit to an Integer using #to_int" do # TODO + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + + seen = [] + @io.send(@method, '>', obj) { |s| seen << s } + seen.should == ["this>", "is>", "an>", "examp", "le"] + end +end diff --git a/spec/ruby/library/stringio/shared/each_byte.rb b/spec/ruby/library/stringio/shared/each_byte.rb new file mode 100644 index 0000000000..b51fa38f2f --- /dev/null +++ b/spec/ruby/library/stringio/shared/each_byte.rb @@ -0,0 +1,48 @@ +describe :stringio_each_byte, shared: true do + before :each do + @io = StringIO.new("xyz") + end + + it "yields each character code in turn" do + seen = [] + @io.send(@method) { |b| seen << b } + seen.should == [120, 121, 122] + end + + it "updates the position before each yield" do + seen = [] + @io.send(@method) { |b| seen << @io.pos } + seen.should == [1, 2, 3] + end + + it "does not yield if the current position is out of bounds" do + @io.pos = 1000 + seen = nil + @io.send(@method) { |b| seen = b } + seen.should be_nil + end + + it "returns self" do + @io.send(@method) {}.should equal(@io) + end + + it "returns an Enumerator when passed no block" do + enum = @io.send(@method) + enum.instance_of?(Enumerator).should be_true + + seen = [] + enum.each { |b| seen << b } + seen.should == [120, 121, 122] + end +end + +describe :stringio_each_byte_not_readable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"xyz", "w") + -> { io.send(@method) { |b| b } }.should raise_error(IOError) + + io = StringIO.new("xyz") + io.close_read + -> { io.send(@method) { |b| b } }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/shared/each_char.rb b/spec/ruby/library/stringio/shared/each_char.rb new file mode 100644 index 0000000000..197237c1c8 --- /dev/null +++ b/spec/ruby/library/stringio/shared/each_char.rb @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- +describe :stringio_each_char, shared: true do + before :each do + @io = StringIO.new("xyz äöü") + end + + it "yields each character code in turn" do + seen = [] + @io.send(@method) { |c| seen << c } + seen.should == ["x", "y", "z", " ", "ä", "ö", "ü"] + end + + it "returns self" do + @io.send(@method) {}.should equal(@io) + end + + it "returns an Enumerator when passed no block" do + enum = @io.send(@method) + enum.instance_of?(Enumerator).should be_true + + seen = [] + enum.each { |c| seen << c } + seen.should == ["x", "y", "z", " ", "ä", "ö", "ü"] + end +end + +describe :stringio_each_char_not_readable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"xyz", "w") + -> { io.send(@method) { |b| b } }.should raise_error(IOError) + + io = StringIO.new("xyz") + io.close_read + -> { io.send(@method) { |b| b } }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/shared/eof.rb b/spec/ruby/library/stringio/shared/eof.rb new file mode 100644 index 0000000000..e0368a2892 --- /dev/null +++ b/spec/ruby/library/stringio/shared/eof.rb @@ -0,0 +1,24 @@ +describe :stringio_eof, shared: true do + before :each do + @io = StringIO.new("eof") + end + + it "returns true when self's position is greater than or equal to self's size" do + @io.pos = 3 + @io.send(@method).should be_true + + @io.pos = 6 + @io.send(@method).should be_true + end + + it "returns false when self's position is less than self's size" do + @io.pos = 0 + @io.send(@method).should be_false + + @io.pos = 1 + @io.send(@method).should be_false + + @io.pos = 2 + @io.send(@method).should be_false + end +end diff --git a/spec/ruby/library/stringio/shared/getc.rb b/spec/ruby/library/stringio/shared/getc.rb new file mode 100644 index 0000000000..ba65040bce --- /dev/null +++ b/spec/ruby/library/stringio/shared/getc.rb @@ -0,0 +1,43 @@ +describe :stringio_getc, shared: true do + before :each do + @io = StringIO.new("example") + end + + it "increases self's position by one" do + @io.send(@method) + @io.pos.should eql(1) + + @io.send(@method) + @io.pos.should eql(2) + + @io.send(@method) + @io.pos.should eql(3) + end + + it "returns nil when called at the end of self" do + @io.pos = 7 + @io.send(@method).should be_nil + @io.send(@method).should be_nil + @io.send(@method).should be_nil + end + + it "does not increase self's position when called at the end of file" do + @io.pos = 7 + @io.send(@method) + @io.pos.should eql(7) + + @io.send(@method) + @io.pos.should eql(7) + end +end + +describe :stringio_getc_not_readable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"xyz", "w") + -> { io.send(@method) }.should raise_error(IOError) + + io = StringIO.new("xyz") + io.close_read + -> { io.send(@method) }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/shared/gets.rb b/spec/ruby/library/stringio/shared/gets.rb new file mode 100644 index 0000000000..8396b161f1 --- /dev/null +++ b/spec/ruby/library/stringio/shared/gets.rb @@ -0,0 +1,249 @@ +describe :stringio_gets_separator, shared: true do + describe "when passed [separator]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read till the next occurrence of the passed separator" do + @io.send(@method, ">").should == "this>" + @io.send(@method, ">").should == "is>" + @io.send(@method, ">").should == "an>" + @io.send(@method, ">").should == "example" + end + + it "sets $_ to the read content" do + @io.send(@method, ">") + $_.should == "this>" + @io.send(@method, ">") + $_.should == "is>" + @io.send(@method, ">") + $_.should == "an>" + @io.send(@method, ">") + $_.should == "example" + end + + it "accepts string as separator" do + @io.send(@method, "is>") + $_.should == "this>" + @io.send(@method, "an>") + $_.should == "is>an>" + @io.send(@method, "example") + $_.should == "example" + end + + it "updates self's lineno by one" do + @io.send(@method, ">") + @io.lineno.should eql(1) + + @io.send(@method, ">") + @io.lineno.should eql(2) + + @io.send(@method, ">") + @io.lineno.should eql(3) + end + + it "returns the next paragraph when the passed separator is an empty String" do + io = StringIO.new("this is\n\nan example") + io.send(@method, "").should == "this is\n\n" + io.send(@method, "").should == "an example" + end + + it "returns the remaining content starting at the current position when passed nil" do + io = StringIO.new("this is\n\nan example") + io.pos = 5 + io.send(@method, nil).should == "is\n\nan example" + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return(">") + @io.send(@method, obj).should == "this>" + end + end +end + +describe :stringio_gets_limit, shared: true do + describe "when passed [limit]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read until the limit is met" do + @io.send(@method, 4).should == "this" + @io.send(@method, 3).should == ">is" + @io.send(@method, 5).should == ">an>e" + @io.send(@method, 6).should == "xample" + end + + it "sets $_ to the read content" do + @io.send(@method, 4) + $_.should == "this" + @io.send(@method, 3) + $_.should == ">is" + @io.send(@method, 5) + $_.should == ">an>e" + @io.send(@method, 6) + $_.should == "xample" + end + + it "updates self's lineno by one" do + @io.send(@method, 3) + @io.lineno.should eql(1) + + @io.send(@method, 3) + @io.lineno.should eql(2) + + @io.send(@method, 3) + @io.lineno.should eql(3) + end + + it "tries to convert the passed limit to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(4) + @io.send(@method, obj).should == "this" + end + + it "returns a blank string when passed a limit of 0" do + @io.send(@method, 0).should == "" + end + + it "ignores it when passed a negative limit" do + @io.send(@method, -4).should == "this>is>an>example" + end + end +end + +describe :stringio_gets_separator_and_limit, shared: true do + describe "when passed [separator] and [limit]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read until the limit is consumed or the separator is met" do + @io.send(@method, '>', 8).should == "this>" + @io.send(@method, '>', 2).should == "is" + @io.send(@method, '>', 10).should == ">" + @io.send(@method, '>', 6).should == "an>" + @io.send(@method, '>', 5).should == "examp" + end + + it "truncates the multi-character separator at the end to meet the limit" do + @io.send(@method, "is>an", 7).should == "this>is" + end + + it "sets $_ to the read content" do + @io.send(@method, '>', 8) + $_.should == "this>" + @io.send(@method, '>', 2) + $_.should == "is" + @io.send(@method, '>', 10) + $_.should == ">" + @io.send(@method, '>', 6) + $_.should == "an>" + @io.send(@method, '>', 5) + $_.should == "examp" + end + + it "updates self's lineno by one" do + @io.send(@method, '>', 3) + @io.lineno.should eql(1) + + @io.send(@method, '>', 3) + @io.lineno.should eql(2) + + @io.send(@method, '>', 3) + @io.lineno.should eql(3) + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return('>') + @io.send(@method, obj, 5).should == "this>" + end + + it "does not raise TypeError if passed separator is nil" do + @io.send(@method, nil, 5).should == "this>" + end + + it "tries to convert the passed limit to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + @io.send(@method, '>', obj).should == "this>" + end + end +end + +describe :stringio_gets_no_argument, shared: true do + describe "when passed no argument" do + before :each do + @io = StringIO.new("this is\nan example\nfor StringIO#gets") + end + + it "returns the data read till the next occurrence of $/ or till eof" do + @io.send(@method).should == "this is\n" + + begin + old_sep = $/ + suppress_warning {$/ = " "} + @io.send(@method).should == "an " + @io.send(@method).should == "example\nfor " + @io.send(@method).should == "StringIO#gets" + ensure + suppress_warning {$/ = old_sep} + end + end + + it "sets $_ to the read content" do + @io.send(@method) + $_.should == "this is\n" + @io.send(@method) + $_.should == "an example\n" + @io.send(@method) + $_.should == "for StringIO#gets" + end + + it "updates self's position" do + @io.send(@method) + @io.pos.should eql(8) + + @io.send(@method) + @io.pos.should eql(19) + + @io.send(@method) + @io.pos.should eql(36) + end + + it "updates self's lineno" do + @io.send(@method) + @io.lineno.should eql(1) + + @io.send(@method) + @io.lineno.should eql(2) + + @io.send(@method) + @io.lineno.should eql(3) + end + end +end + +describe :stringio_gets_chomp, shared: true do + describe "when passed [chomp]" do + it "returns the data read without a trailing newline character" do + io = StringIO.new("this>is>an>example\n") + io.send(@method, chomp: true).should == "this>is>an>example" + end + end +end + +describe :stringio_gets_write_only, shared: true do + describe "when in write-only mode" do + it "raises an IOError" do + io = StringIO.new(+"xyz", "w") + -> { io.send(@method) }.should raise_error(IOError) + + io = StringIO.new("xyz") + io.close_read + -> { io.send(@method) }.should raise_error(IOError) + end + end +end diff --git a/spec/ruby/library/stringio/shared/isatty.rb b/spec/ruby/library/stringio/shared/isatty.rb new file mode 100644 index 0000000000..c9e7ee7321 --- /dev/null +++ b/spec/ruby/library/stringio/shared/isatty.rb @@ -0,0 +1,5 @@ +describe :stringio_isatty, shared: true do + it "returns false" do + StringIO.new("tty").send(@method).should be_false + end +end diff --git a/spec/ruby/library/stringio/shared/length.rb b/spec/ruby/library/stringio/shared/length.rb new file mode 100644 index 0000000000..60a4eb1bdd --- /dev/null +++ b/spec/ruby/library/stringio/shared/length.rb @@ -0,0 +1,5 @@ +describe :stringio_length, shared: true do + it "returns the length of the wrapped string" do + StringIO.new("example").send(@method).should == 7 + end +end diff --git a/spec/ruby/library/stringio/shared/read.rb b/spec/ruby/library/stringio/shared/read.rb new file mode 100644 index 0000000000..22f76b0fb0 --- /dev/null +++ b/spec/ruby/library/stringio/shared/read.rb @@ -0,0 +1,145 @@ +describe :stringio_read, shared: true do + before :each do + @io = StringIO.new("example") + end + + it "returns the passed buffer String" do + # Note: Rubinius bug: + # @io.send(@method, 7, buffer = +"").should equal(buffer) + ret = @io.send(@method, 7, buffer = +"") + ret.should equal(buffer) + end + + it "reads length bytes and writes them to the buffer String" do + @io.send(@method, 7, buffer = +"").should.equal?(buffer) + buffer.should == "example" + end + + guard -> { StringIO::VERSION < "3.1.2" } do + it "does not preserve the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @io.send(@method, 7, buffer) + + buffer.encoding.should_not == Encoding::ISO_8859_1 + end + end + + guard -> { StringIO::VERSION >= "3.1.2" } do + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @io.send(@method, 7, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + end + + it "tries to convert the passed buffer Object to a String using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(buffer = +"") + + @io.send(@method, 7, obj) + buffer.should == "example" + end + + it "raises a TypeError when the passed buffer Object can't be converted to a String" do + -> { @io.send(@method, 7, Object.new) }.should raise_error(TypeError) + end + + it "raises a FrozenError error when passed a frozen String as buffer" do + -> { @io.send(@method, 7, "".freeze) }.should raise_error(FrozenError) + end +end + +describe :stringio_read_length, shared: true do + before :each do + @io = StringIO.new("example") + end + + it "reads length bytes from the current position and returns them" do + @io.pos = 3 + @io.send(@method, 4).should == "mple" + end + + it "reads at most the whole content" do + @io.send(@method, 999).should == "example" + end + + it "correctly updates the position" do + @io.send(@method, 3) + @io.pos.should eql(3) + + @io.send(@method, 999) + @io.pos.should eql(7) + end + + it "tries to convert the passed length to an Integer using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(7) + @io.send(@method, obj).should == "example" + end + + it "raises a TypeError when the passed length can't be converted to an Integer" do + -> { @io.send(@method, Object.new) }.should raise_error(TypeError) + end + + it "raises a TypeError when the passed length is negative" do + -> { @io.send(@method, -2) }.should raise_error(ArgumentError) + end + + it "returns a binary String" do + @io.send(@method, 4).encoding.should == Encoding::BINARY + end +end + +describe :stringio_read_no_arguments, shared: true do + before :each do + @io = StringIO.new(+"example") + end + + it "reads the whole content starting from the current position" do + @io.send(@method).should == "example" + + @io.pos = 3 + @io.send(@method).should == "mple" + end + + it "correctly updates the current position" do + @io.send(@method) + @io.pos.should eql(7) + end + + it "correctly update the current position in bytes when multi-byte characters are used" do + @io.print("example\u03A3") # Overwrite the original string with 8 characters containing 9 bytes. + @io.send(@method) + @io.pos.should eql(9) + end +end + +describe :stringio_read_nil, shared: true do + before :each do + @io = StringIO.new("example") + end + + it "returns the remaining content from the current position" do + @io.send(@method, nil).should == "example" + + @io.pos = 4 + @io.send(@method, nil).should == "ple" + end + + it "updates the current position" do + @io.send(@method, nil) + @io.pos.should eql(7) + end +end + +describe :stringio_read_not_readable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"test", "w") + -> { io.send(@method) }.should raise_error(IOError) + + io = StringIO.new("test") + io.close_read + -> { io.send(@method) }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/shared/readchar.rb b/spec/ruby/library/stringio/shared/readchar.rb new file mode 100644 index 0000000000..72d7446c36 --- /dev/null +++ b/spec/ruby/library/stringio/shared/readchar.rb @@ -0,0 +1,29 @@ +describe :stringio_readchar, shared: true do + before :each do + @io = StringIO.new("example") + end + + it "correctly updates the current position" do + @io.send(@method) + @io.pos.should == 1 + + @io.send(@method) + @io.pos.should == 2 + end + + it "raises an EOFError when self is at the end" do + @io.pos = 7 + -> { @io.send(@method) }.should raise_error(EOFError) + end +end + +describe :stringio_readchar_not_readable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"a b c d e", "w") + -> { io.send(@method) }.should raise_error(IOError) + + io = StringIO.new("a b c d e") + io.close_read + -> { io.send(@method) }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/shared/sysread.rb b/spec/ruby/library/stringio/shared/sysread.rb new file mode 100644 index 0000000000..3e23fbc233 --- /dev/null +++ b/spec/ruby/library/stringio/shared/sysread.rb @@ -0,0 +1,15 @@ +describe :stringio_sysread_length, shared: true do + before :each do + @io = StringIO.new("example") + end + + it "returns an empty String when passed 0 and no data remains" do + @io.send(@method, 8).should == "example" + @io.send(@method, 0).should == "" + end + + it "raises an EOFError when passed length > 0 and no data remains" do + @io.read.should == "example" + -> { @io.send(@method, 1) }.should raise_error(EOFError) + end +end diff --git a/spec/ruby/library/stringio/shared/tell.rb b/spec/ruby/library/stringio/shared/tell.rb new file mode 100644 index 0000000000..852c51c192 --- /dev/null +++ b/spec/ruby/library/stringio/shared/tell.rb @@ -0,0 +1,12 @@ +describe :stringio_tell, shared: true do + before :each do + @io = StringIOSpecs.build + end + + it "returns the current byte offset" do + @io.getc + @io.send(@method).should == 1 + @io.read(7) + @io.send(@method).should == 8 + end +end diff --git a/spec/ruby/library/stringio/shared/write.rb b/spec/ruby/library/stringio/shared/write.rb new file mode 100644 index 0000000000..4661658baf --- /dev/null +++ b/spec/ruby/library/stringio/shared/write.rb @@ -0,0 +1,135 @@ +describe :stringio_write, shared: true do + before :each do + @io = StringIO.new(+'12345') + end + + it "tries to convert the passed Object to a String using #to_s" do + obj = mock("to_s") + obj.should_receive(:to_s).and_return("to_s") + @io.send(@method, obj) + @io.string.should == "to_s5" + end +end + +describe :stringio_write_string, shared: true do + before :each do + @io = StringIO.new(+'12345') + end + + # TODO: RDoc says that #write appends at the current position. + it "writes the passed String at the current buffer position" do + @io.pos = 2 + @io.send(@method, 'x').should == 1 + @io.string.should == '12x45' + @io.send(@method, 7).should == 1 + @io.string.should == '12x75' + end + + it "pads self with \\000 when the current position is after the end" do + @io.pos = 8 + @io.send(@method, 'x') + @io.string.should == "12345\000\000\000x" + @io.send(@method, 9) + @io.string.should == "12345\000\000\000x9" + end + + it "returns the number of bytes written" do + @io.send(@method, '').should == 0 + @io.send(@method, nil).should == 0 + str = "1" * 100 + @io.send(@method, str).should == 100 + end + + it "updates self's position" do + @io.send(@method, 'test') + @io.pos.should eql(4) + end + + it "handles concurrent writes correctly" do + @io = StringIO.new + n = 8 + go = false + threads = n.times.map { |i| + Thread.new { + Thread.pass until go + @io.write i.to_s + } + } + go = true + threads.each(&:join) + @io.string.size.should == n.times.map(&:to_s).join.size + end + + it "handles writing non-ASCII UTF-8 after seek" do + @io.binmode + @io << "\x80" + @io.pos = 0 + @io << "\x81" + @io.string.should == "\x812345".b + end + + it "handles writing with position < buffer size" do + @io.pos = 2 + @io.write "abc" + @io.string.should == "12abc" + + @io.pos = 2 + @io.write "de" + @io.string.should == "12dec" + + @io.pos = 2 + @io.write "fghi" + @io.string.should == "12fghi" + end + + it "transcodes the given string when the external encoding is set and neither is BINARY" do + utf8_str = "hello" + io = StringIO.new.set_encoding(Encoding::UTF_16BE) + io.external_encoding.should == Encoding::UTF_16BE + + io.send(@method, utf8_str) + + expected = [0, 104, 0, 101, 0, 108, 0, 108, 0, 111] # UTF-16BE bytes for "hello" + io.string.bytes.should == expected + end + + it "does not transcode the given string when the external encoding is set and the string encoding is BINARY" do + str = "été_".b + io = StringIO.new.set_encoding(Encoding::UTF_16BE) + io.external_encoding.should == Encoding::UTF_16BE + + io.send(@method, str) + + io.string.bytes.should == str.bytes + end +end + +describe :stringio_write_not_writable, shared: true do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io.send(@method, "test") }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io.send(@method, "test") }.should raise_error(IOError) + end +end + +describe :stringio_write_append, shared: true do + before :each do + @io = StringIO.new(+"example", "a") + end + + it "appends the passed argument to the end of self" do + @io.send(@method, ", just testing") + @io.string.should == "example, just testing" + + @io.send(@method, " and more testing") + @io.string.should == "example, just testing and more testing" + end + + it "correctly updates self's position" do + @io.send(@method, ", testing") + @io.pos.should eql(16) + end +end diff --git a/spec/ruby/library/stringio/size_spec.rb b/spec/ruby/library/stringio/size_spec.rb new file mode 100644 index 0000000000..f674d22db9 --- /dev/null +++ b/spec/ruby/library/stringio/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/length' + +describe "StringIO#size" do + it_behaves_like :stringio_length, :size +end diff --git a/spec/ruby/library/stringio/string_spec.rb b/spec/ruby/library/stringio/string_spec.rb new file mode 100644 index 0000000000..1ed5233ba6 --- /dev/null +++ b/spec/ruby/library/stringio/string_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#string" do + it "returns the underlying string" do + io = StringIO.new(str = "hello") + io.string.should equal(str) + end +end + +describe "StringIO#string=" do + before :each do + @io = StringIO.new("example\nstring") + end + + it "returns the passed String" do + str = "test" + (@io.string = str).should equal(str) + end + + it "changes the underlying string" do + str = "hello" + @io.string = str + @io.string.should equal(str) + end + + it "resets the position" do + @io.pos = 1 + @io.string = "other" + @io.pos.should eql(0) + end + + it "resets the line number" do + @io.lineno = 1 + @io.string = "other" + @io.lineno.should eql(0) + end + + it "tries to convert the passed Object to a String using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return("to_str") + + @io.string = obj + @io.string.should == "to_str" + end + + it "raises a TypeError when the passed Object can't be converted to an Integer" do + -> { @io.seek(Object.new) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/stringio/stringio_spec.rb b/spec/ruby/library/stringio/stringio_spec.rb new file mode 100644 index 0000000000..5ef42b3390 --- /dev/null +++ b/spec/ruby/library/stringio/stringio_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require "stringio" + +describe "StringIO" do + it "includes the Enumerable module" do + StringIO.should include(Enumerable) + end +end diff --git a/spec/ruby/library/stringio/sync_spec.rb b/spec/ruby/library/stringio/sync_spec.rb new file mode 100644 index 0000000000..e717a5697b --- /dev/null +++ b/spec/ruby/library/stringio/sync_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#sync" do + it "returns true" do + StringIO.new('').sync.should be_true + end +end + +describe "StringIO#sync=" do + before :each do + @io = StringIO.new('') + end + + it "does not change 'sync' status" do + @io.sync = false + @io.sync.should be_true + end +end diff --git a/spec/ruby/library/stringio/sysread_spec.rb b/spec/ruby/library/stringio/sysread_spec.rb new file mode 100644 index 0000000000..fabb06dd9a --- /dev/null +++ b/spec/ruby/library/stringio/sysread_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require "stringio" +require_relative 'shared/read' +require_relative 'shared/sysread' + +describe "StringIO#sysread when passed length, buffer" do + it_behaves_like :stringio_read, :sysread +end + +describe "StringIO#sysread when passed [length]" do + it_behaves_like :stringio_read_length, :sysread +end + +describe "StringIO#sysread when passed no arguments" do + it_behaves_like :stringio_read_no_arguments, :sysread + + it "returns an empty String if at EOF" do + @io.sysread.should == "example" + @io.sysread.should == "" + end +end + +describe "StringIO#sysread when self is not readable" do + it_behaves_like :stringio_read_not_readable, :sysread +end + +describe "StringIO#sysread when passed nil" do + it_behaves_like :stringio_read_nil, :sysread + + it "returns an empty String if at EOF" do + @io.sysread(nil).should == "example" + @io.sysread(nil).should == "" + end +end + +describe "StringIO#sysread when passed length" do + it_behaves_like :stringio_sysread_length, :sysread +end + +describe "StringIO#sysread when passed [length]" do + before :each do + @io = StringIO.new("example") + end + + it "raises an EOFError when self's position is at the end" do + @io.pos = 7 + -> { @io.sysread(10) }.should raise_error(EOFError) + end + + it "returns an empty String when length is 0" do + @io.sysread(0).should == "" + end +end diff --git a/spec/ruby/library/stringio/syswrite_spec.rb b/spec/ruby/library/stringio/syswrite_spec.rb new file mode 100644 index 0000000000..c4891e669b --- /dev/null +++ b/spec/ruby/library/stringio/syswrite_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/write' + +describe "StringIO#syswrite when passed [Object]" do + it_behaves_like :stringio_write, :syswrite +end + +describe "StringIO#syswrite when passed [String]" do + it_behaves_like :stringio_write_string, :syswrite +end + +describe "StringIO#syswrite when self is not writable" do + it_behaves_like :stringio_write_not_writable, :syswrite +end + +describe "StringIO#syswrite when in append mode" do + it_behaves_like :stringio_write_append, :syswrite +end diff --git a/spec/ruby/library/stringio/tell_spec.rb b/spec/ruby/library/stringio/tell_spec.rb new file mode 100644 index 0000000000..8350ee6f4d --- /dev/null +++ b/spec/ruby/library/stringio/tell_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/tell' + +describe "StringIO#tell" do + it_behaves_like :stringio_tell, :tell +end diff --git a/spec/ruby/library/stringio/truncate_spec.rb b/spec/ruby/library/stringio/truncate_spec.rb new file mode 100644 index 0000000000..592ca5a6e1 --- /dev/null +++ b/spec/ruby/library/stringio/truncate_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' +require "stringio" + +describe "StringIO#truncate when passed [length]" do + before :each do + @io = StringIO.new(+'123456789') + end + + it "returns an Integer" do + @io.truncate(4).should be_kind_of(Integer) + end + + it "truncated the underlying string down to the passed length" do + @io.truncate(4) + @io.string.should == "1234" + end + + it "does not create a copy of the underlying string" do + io = StringIO.new(str = +"123456789") + io.truncate(4) + io.string.should equal(str) + end + + it "does not change the position" do + @io.pos = 7 + @io.truncate(4) + @io.pos.should eql(7) + end + + it "can grow a string to a larger size, padding it with \\000" do + @io.truncate(12) + @io.string.should == "123456789\000\000\000" + end + + it "raises an Errno::EINVAL when the passed length is negative" do + -> { @io.truncate(-1) }.should raise_error(Errno::EINVAL) + -> { @io.truncate(-10) }.should raise_error(Errno::EINVAL) + end + + it "tries to convert the passed length to an Integer using #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(4) + + @io.truncate(obj) + @io.string.should == "1234" + end + + it "raises a TypeError when the passed length can't be converted to an Integer" do + -> { @io.truncate(Object.new) }.should raise_error(TypeError) + end +end + +describe "StringIO#truncate when self is not writable" do + it "raises an IOError" do + io = StringIO.new(+"test", "r") + -> { io.truncate(2) }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.close_write + -> { io.truncate(2) }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/stringio/tty_spec.rb b/spec/ruby/library/stringio/tty_spec.rb new file mode 100644 index 0000000000..c6293dcbd7 --- /dev/null +++ b/spec/ruby/library/stringio/tty_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/isatty' + +describe "StringIO#tty?" do + it_behaves_like :stringio_isatty, :tty? +end diff --git a/spec/ruby/library/stringio/ungetbyte_spec.rb b/spec/ruby/library/stringio/ungetbyte_spec.rb new file mode 100644 index 0000000000..87b27b837e --- /dev/null +++ b/spec/ruby/library/stringio/ungetbyte_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: false +require_relative '../../spec_helper' +require 'stringio' + +describe "StringIO#ungetbyte" do + it "ungets a single byte from a string starting with a single byte character" do + str = 'This is a simple string.' + io = StringIO.new("#{str}") + c = io.getc + c.should == 'T' + io.ungetbyte(83) + io.string.should == 'Shis is a simple string.' + end + + it "ungets a single byte from a string in the middle of a multibyte character" do + str = "\u01a9" + io = StringIO.new(str) + b = io.getbyte + b.should == 0xc6 # First byte of UTF-8 encoding of \u01a9 + io.ungetbyte(0xce) # First byte of UTF-8 encoding of \u03a9 + io.string.should == "\u03a9" + end + + it "constrains the value of a numeric argument to a single byte" do + str = 'This is a simple string.' + io = StringIO.new("#{str}") + c = io.getc + c.should == 'T' + io.ungetbyte(83 | 0xff00) + io.string.should == 'Shis is a simple string.' + end + + it "ungets the bytes of a string if given a string as an argument" do + str = "\u01a9" + io = StringIO.new(str) + b = io.getbyte + b.should == 0xc6 # First byte of UTF-8 encoding of \u01a9 + io.ungetbyte("\u01a9") + io.string.bytes.should == [198, 169, 169] + end + +end diff --git a/spec/ruby/library/stringio/ungetc_spec.rb b/spec/ruby/library/stringio/ungetc_spec.rb new file mode 100644 index 0000000000..bceafa79ff --- /dev/null +++ b/spec/ruby/library/stringio/ungetc_spec.rb @@ -0,0 +1,72 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "StringIO#ungetc when passed [char]" do + before :each do + @io = StringIO.new(+'1234') + end + + it "writes the passed char before the current position" do + @io.pos = 1 + @io.ungetc(?A) + @io.string.should == 'A234' + end + + it "returns nil" do + @io.pos = 1 + @io.ungetc(?A).should be_nil + end + + it "decreases the current position by one" do + @io.pos = 2 + @io.ungetc(?A) + @io.pos.should eql(1) + end + + it "pads with \\000 when the current position is after the end" do + @io.pos = 15 + @io.ungetc(?A) + @io.string.should == "1234\000\000\000\000\000\000\000\000\000\000A" + end + + it "tries to convert the passed argument to an String using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(?A) + + @io.pos = 1 + @io.ungetc(obj) + @io.string.should == "A234" + end + + it "raises a TypeError when the passed length can't be converted to an Integer or String" do + -> { @io.ungetc(Object.new) }.should raise_error(TypeError) + end +end + +describe "StringIO#ungetc when self is not readable" do + it "raises an IOError" do + io = StringIO.new(+"test", "w") + io.pos = 1 + -> { io.ungetc(?A) }.should raise_error(IOError) + + io = StringIO.new(+"test") + io.pos = 1 + io.close_read + -> { io.ungetc(?A) }.should raise_error(IOError) + end +end + +# Note: This is incorrect. +# +# describe "StringIO#ungetc when self is not writable" do +# it "raises an IOError" do +# io = StringIO.new(+"test", "r") +# io.pos = 1 +# lambda { io.ungetc(?A) }.should raise_error(IOError) +# +# io = StringIO.new(+"test") +# io.pos = 1 +# io.close_write +# lambda { io.ungetc(?A) }.should raise_error(IOError) +# end +# end diff --git a/spec/ruby/library/stringio/write_nonblock_spec.rb b/spec/ruby/library/stringio/write_nonblock_spec.rb new file mode 100644 index 0000000000..b48ef6698a --- /dev/null +++ b/spec/ruby/library/stringio/write_nonblock_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/write' + +describe "StringIO#write_nonblock when passed [Object]" do + it_behaves_like :stringio_write, :write_nonblock +end + +describe "StringIO#write_nonblock when passed [String]" do + it_behaves_like :stringio_write_string, :write_nonblock + + it "accepts :exception option" do + io = StringIO.new(+"12345", "a") + io.write_nonblock("67890", exception: true) + io.string.should == "1234567890" + end +end + +describe "StringIO#write_nonblock when self is not writable" do + it_behaves_like :stringio_write_not_writable, :write_nonblock +end + +describe "StringIO#write_nonblock when in append mode" do + it_behaves_like :stringio_write_append, :write_nonblock +end diff --git a/spec/ruby/library/stringio/write_spec.rb b/spec/ruby/library/stringio/write_spec.rb new file mode 100644 index 0000000000..3f755c32b4 --- /dev/null +++ b/spec/ruby/library/stringio/write_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/write' + +describe "StringIO#write when passed [Object]" do + it_behaves_like :stringio_write, :write +end + +describe "StringIO#write when passed [String]" do + it_behaves_like :stringio_write_string, :write +end + +describe "StringIO#write when self is not writable" do + it_behaves_like :stringio_write_not_writable, :write +end + +describe "StringIO#write when in append mode" do + it_behaves_like :stringio_write_append, :write +end diff --git a/spec/ruby/library/stringscanner/append_spec.rb b/spec/ruby/library/stringscanner/append_spec.rb new file mode 100644 index 0000000000..fef5dcf2bd --- /dev/null +++ b/spec/ruby/library/stringscanner/append_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/concat' +require 'strscan' + +describe "StringScanner#<<" do + it_behaves_like :strscan_concat, :<< +end + +describe "StringScanner#<< when passed an Integer" do + it_behaves_like :strscan_concat_fixnum, :<< +end diff --git a/spec/ruby/library/stringscanner/beginning_of_line_spec.rb b/spec/ruby/library/stringscanner/beginning_of_line_spec.rb new file mode 100644 index 0000000000..3f6f0da75f --- /dev/null +++ b/spec/ruby/library/stringscanner/beginning_of_line_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/bol' +require 'strscan' + +describe "StringScanner#beginning_of_line?" do + it_behaves_like :strscan_bol, :beginning_of_line? +end diff --git a/spec/ruby/library/stringscanner/bol_spec.rb b/spec/ruby/library/stringscanner/bol_spec.rb new file mode 100644 index 0000000000..d31766e0e2 --- /dev/null +++ b/spec/ruby/library/stringscanner/bol_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/bol' +require 'strscan' + +describe "StringScanner#bol?" do + it_behaves_like :strscan_bol, :bol? +end diff --git a/spec/ruby/library/stringscanner/captures_spec.rb b/spec/ruby/library/stringscanner/captures_spec.rb new file mode 100644 index 0000000000..bdfb0e0cc5 --- /dev/null +++ b/spec/ruby/library/stringscanner/captures_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#captures" do + before do + @s = StringScanner.new('Fri Dec 12 1975 14:39') + end + + it "returns the array of captured values of the most recent matching" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.captures.should == ["Fri", "Dec", "12"] + end + + it "returns nil if the last match fails" do + @s.scan(/nope/) + @s.captures.should == nil + end + + it "returns nil if there is no any match done" do + @s.captures.should == nil + end + + version_is StringScanner::Version, ""..."3.0.8" do # ruby_version_is ""..."3.3.3" + it "returns '' for an optional capturing group if it doesn't match" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\s+)?/) + @s.captures.should == ["Fri", "Dec", ""] + end + end + + version_is StringScanner::Version, "3.0.8" do # ruby_version_is "3.3.3" + it "returns nil for an optional capturing group if it doesn't match" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\s+)?/) + @s.captures.should == ["Fri", "Dec", nil] + end + end +end diff --git a/spec/ruby/library/stringscanner/charpos_spec.rb b/spec/ruby/library/stringscanner/charpos_spec.rb new file mode 100644 index 0000000000..9aa5b00dd9 --- /dev/null +++ b/spec/ruby/library/stringscanner/charpos_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#charpos" do + it "returns character index corresponding to the current position" do + s = StringScanner.new("abc") + + s.scan_until(/b/) + s.charpos.should == 2 + end + + it "is multi-byte character sensitive" do + s = StringScanner.new("abcädeföghi") + + s.scan_until(/ö/) + s.charpos.should == 8 + end +end diff --git a/spec/ruby/library/stringscanner/check_spec.rb b/spec/ruby/library/stringscanner/check_spec.rb new file mode 100644 index 0000000000..5e855e154a --- /dev/null +++ b/spec/ruby/library/stringscanner/check_spec.rb @@ -0,0 +1,93 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#check" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the value that scan would return, without advancing the scan pointer" do + @s.check(/This/).should == "This" + @s.matched.should == "This" + @s.pos.should == 0 + @s.check(/is/).should == nil + @s.matched.should == nil + end + + it "treats String as the pattern itself" do + @s.check("This").should == "This" + @s.matched.should == "This" + @s.pos.should == 0 + @s.check(/is/).should == nil + @s.matched.should == nil + end + + describe "#[] successive call with a capture group name" do + context "when #check was called with a Regexp pattern" do + it "returns matched substring when matching succeeded" do + @s.check(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.check(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end + + context "when #check was called with a String pattern" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil when matching succeeded" do + @s.check("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "raises IndexError when matching succeeded" do + @s.check("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + @s.check("2008") + @s.should_not.matched? + @s[:a].should be_nil + end + + it "returns a matching substring when given Integer index" do + @s.check("This") + @s[0].should == "This" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.check("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.check("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/check_until_spec.rb b/spec/ruby/library/stringscanner/check_until_spec.rb new file mode 100644 index 0000000000..582da66b37 --- /dev/null +++ b/spec/ruby/library/stringscanner/check_until_spec.rb @@ -0,0 +1,129 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#check_until" do + before do + @s = StringScanner.new("This is a test") + end + + it "returns the same value of #scan_until, but don't advances the scan pointer" do + @s.check_until(/a/).should == "This is a" + @s.pos.should == 0 + @s.check_until(/test/).should == "This is a test" + end + + it "sets the last match result" do + @s.check_until(/a/) + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + + version_is StringScanner::Version, ""..."3.1.1" do # ruby_version_is ""..."3.4" + it "raises TypeError if given a String" do + -> { + @s.check_until('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + it "searches a substring in the rest part of a string if given a String" do + @s.check_until("a").should == "This is a" + @s.pos.should == 0 + end + + # https://github.com/ruby/strscan/issues/131 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" + it "sets the last match result if given a String" do + @s.check_until("a") + + @s.pre_match.should == "" + @s.matched.should == "This is a" + @s.post_match.should == " test" + end + end + + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "sets the last match result if given a String" do + @s.check_until("a") + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + end + end + + describe "#[] successive call with a capture group name" do + context "when #check_until was called with a Regexp pattern" do + it "returns matched substring when matching succeeded" do + @s.check_until(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.check_until(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + context "when #check_until was called with a String pattern" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil when matching succeeded" do + @s.check_until("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError when matching succeeded" do + @s.check_until("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + @s.check_until("2008") + @s.should_not.matched? + @s[:a].should be_nil + end + + it "returns a matching substring when given Integer index" do + @s.check_until("This") + @s[0].should == "This" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.check_until("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.check_until("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/concat_spec.rb b/spec/ruby/library/stringscanner/concat_spec.rb new file mode 100644 index 0000000000..4f790e2505 --- /dev/null +++ b/spec/ruby/library/stringscanner/concat_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/concat' +require 'strscan' + +describe "StringScanner#concat" do + it_behaves_like :strscan_concat, :concat +end + +describe "StringScanner#concat when passed an Integer" do + it_behaves_like :strscan_concat_fixnum, :concat +end diff --git a/spec/ruby/library/stringscanner/dup_spec.rb b/spec/ruby/library/stringscanner/dup_spec.rb new file mode 100644 index 0000000000..0fc52a1477 --- /dev/null +++ b/spec/ruby/library/stringscanner/dup_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#dup" do + before :each do + @string = "this is a test" + @orig_s = StringScanner.new(@string) + end + + it "copies the passed StringScanner's content to self" do + s = @orig_s.dup + s.string.should == @string + end + + it "copies the passed StringScanner's position to self" do + @orig_s.pos = 5 + s = @orig_s.dup + s.pos.should eql(5) + end + + it "copies previous match state" do + @orig_s.scan(/\w+/) + @orig_s.scan(/\s/) + + @orig_s.pre_match.should == "this" + + s = @orig_s.dup + s.pre_match.should == "this" + + s.unscan + s.scan(/\s/).should == " " + end + + it "copies the passed StringScanner scan pointer to self" do + @orig_s.terminate + s = @orig_s.dup + s.eos?.should be_true + end +end diff --git a/spec/ruby/library/stringscanner/element_reference_spec.rb b/spec/ruby/library/stringscanner/element_reference_spec.rb new file mode 100644 index 0000000000..91b6d86dc7 --- /dev/null +++ b/spec/ruby/library/stringscanner/element_reference_spec.rb @@ -0,0 +1,67 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#[]" do + before :each do + @s = StringScanner.new("Fri Jun 13 2008 22:43") + end + + it "returns nil if there is no current match" do + @s[0].should be_nil + @s[:wday].should be_nil + end + + it "returns the n-th subgroup in the most recent match" do + @s.scan(/(\w+) (\w+) (\d+) /) + @s[0].should == "Fri Jun 13 " + @s[1].should == "Fri" + @s[2].should == "Jun" + @s[3].should == "13" + @s[-3].should == "Fri" + @s[-2].should == "Jun" + @s[-1].should == "13" + end + + it "returns nil if index is outside of self" do + @s.scan(/(\w+) (\w+) (\d+) /) + @s[5].should == nil + @s[-5].should == nil + end + + it "calls to_int on the given index" do + @s.scan(/(\w+) (\w+) (\d+) /) + @s[0.5].should == "Fri Jun 13 " + end + + it "raises a TypeError if the given index is nil" do + @s.scan(/(\w+) (\w+) (\d+) /) + -> { @s[nil]}.should raise_error(TypeError) + end + + it "raises a TypeError when a Range is as argument" do + @s.scan(/(\w+) (\w+) (\d+) /) + -> { @s[0..2]}.should raise_error(TypeError) + end + + it "raises a IndexError when there's no any named capture group in the regexp" do + @s.scan(/(\w+) (\w+) (\d+) /) + -> { @s["wday"]}.should raise_error(IndexError) + -> { @s[:wday]}.should raise_error(IndexError) + end + + it "raises a IndexError when given a not existing capture group name" do + @s.scan(/(?<a>\w+) (?<b>\w+) (?<c>\d+) /) + -> { @s["wday"]}.should raise_error(IndexError) + -> { @s[:wday]}.should raise_error(IndexError) + end + + it "returns named capture" do + @s.scan(/(?<wday>\w+) (?<month>\w+) (?<day>\d+) /) + @s["wday"].should == "Fri" + @s["month"].should == "Jun" + @s["day"].should == "13" + @s[:wday].should == "Fri" + @s[:month].should == "Jun" + @s[:day].should == "13" + end +end diff --git a/spec/ruby/library/stringscanner/eos_spec.rb b/spec/ruby/library/stringscanner/eos_spec.rb new file mode 100644 index 0000000000..03c2804e5b --- /dev/null +++ b/spec/ruby/library/stringscanner/eos_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#eos?" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns true if the scan pointer is at the end of the string" do + @s.terminate + @s.should.eos? + + s = StringScanner.new('') + s.should.eos? + end + + it "returns false if the scan pointer is not at the end of the string" do + @s.should_not.eos? + end +end diff --git a/spec/ruby/library/stringscanner/exist_spec.rb b/spec/ruby/library/stringscanner/exist_spec.rb new file mode 100644 index 0000000000..a408fd0b8d --- /dev/null +++ b/spec/ruby/library/stringscanner/exist_spec.rb @@ -0,0 +1,119 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#exist?" do + before do + @s = StringScanner.new("This is a test") + end + + it "returns distance in bytes between the current position and the end of the matched substring" do + @s.exist?(/s/).should == 4 + @s.scan(/This is/) + @s.exist?(/s/).should == 6 + end + + it "returns 0 if the pattern is empty" do + @s.exist?(//).should == 0 + end + + it "returns nil if the pattern isn't found in the string" do + @s.exist?(/S/).should == nil + @s.scan(/This is/) + @s.exist?(/i/).should == nil + end + + it "does not modify the current position" do + @s.pos.should == 0 + @s.exist?(/s/).should == 4 + @s.pos.should == 0 + end + + version_is StringScanner::Version, ""..."3.1.1" do # ruby_version_is ""..."3.4" + it "raises TypeError if given a String" do + -> { + @s.exist?('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + it "searches a substring in the rest part of a string if given a String" do + @s.exist?('a').should == 9 + end + + it "returns nil if the pattern isn't found in the string" do + @s.exist?("S").should == nil + end + end + + describe "#[] successive call with a capture group name" do + context "when #exist? was called with a Regexp pattern" do + it "returns matched substring when matching succeeded" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.exist?(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + context "when #exist? was called with a String pattern" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil when matching succeeded" do + @s.exist?("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError when matching succeeded" do + @s.exist?("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + @s.exist?("2008") + @s.should_not.matched? + @s[:a].should be_nil + end + + it "returns a matching substring when given Integer index" do + @s.exist?("This") + @s[0].should == "This" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.exist?("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.exist?("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/fixed_anchor_spec.rb b/spec/ruby/library/stringscanner/fixed_anchor_spec.rb new file mode 100644 index 0000000000..ce0b714fa8 --- /dev/null +++ b/spec/ruby/library/stringscanner/fixed_anchor_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#fixed_anchor?" do + it "returns whether the fixed-anchor property is set" do + s = StringScanner.new("foo", fixed_anchor: true) + s.should.fixed_anchor? + + s = StringScanner.new("foo", fixed_anchor: false) + s.should_not.fixed_anchor? + end + + it "is set to false by default" do + s = StringScanner.new("foo") + s.should_not.fixed_anchor? + end +end diff --git a/spec/ruby/library/stringscanner/get_byte_spec.rb b/spec/ruby/library/stringscanner/get_byte_spec.rb new file mode 100644 index 0000000000..144859abc9 --- /dev/null +++ b/spec/ruby/library/stringscanner/get_byte_spec.rb @@ -0,0 +1,84 @@ +# encoding: binary +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#get_byte" do + it "scans one byte and returns it" do + s = StringScanner.new('abc5.') + s.get_byte.should == 'a' + s.get_byte.should == 'b' + s.get_byte.should == 'c' + s.get_byte.should == '5' + s.get_byte.should == '.' + end + + it "is not multi-byte character sensitive" do + s = StringScanner.new("\244\242") + s.get_byte.should == "\244" + s.get_byte.should == "\242" + end + + it "returns nil at the end of the string" do + # empty string case + s = StringScanner.new('') + s.get_byte.should == nil + s.get_byte.should == nil + + # non-empty string case + s = StringScanner.new('a') + s.get_byte # skip one + s.get_byte.should == nil + end + + describe "#[] successive call with a capture group name" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil" do + s = StringScanner.new("This is a test") + s.get_byte + s.should.matched? + s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError" do + s = StringScanner.new("This is a test") + s.get_byte + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + + it "returns a matching character when given Integer index" do + s = StringScanner.new("This is a test") + s.get_byte + s[0].should == "T" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("This is a test") + s.exist?(/(?<a>This)/) + s.should.matched? + s[:a].should == "This" + + s.get_byte + s.should.matched? + s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("This is a test") + s.exist?(/(?<a>This)/) + s.should.matched? + s[:a].should == "This" + + s.get_byte + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + end +end diff --git a/spec/ruby/library/stringscanner/getch_spec.rb b/spec/ruby/library/stringscanner/getch_spec.rb new file mode 100644 index 0000000000..d369391b14 --- /dev/null +++ b/spec/ruby/library/stringscanner/getch_spec.rb @@ -0,0 +1,89 @@ +# encoding: binary +require_relative '../../spec_helper' +require_relative 'shared/extract_range' +require 'strscan' + +describe "StringScanner#getch" do + it "scans one character and returns it" do + s = StringScanner.new('abc') + s.getch.should == "a" + s.getch.should == "b" + s.getch.should == "c" + end + + it "is multi-byte character sensitive" do + # Japanese hiragana "A" in EUC-JP + src = "\244\242".dup.force_encoding("euc-jp") + + s = StringScanner.new(src) + s.getch.should == src + end + + it "returns nil at the end of the string" do + # empty string case + s = StringScanner.new('') + s.getch.should == nil + s.getch.should == nil + + # non-empty string case + s = StringScanner.new('a') + s.getch # skip one + s.getch.should == nil + end + + describe "#[] successive call with a capture group name" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil" do + s = StringScanner.new("This is a test") + s.getch + s.should.matched? + s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError" do + s = StringScanner.new("This is a test") + s.getch + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + + it "returns a matching character when given Integer index" do + s = StringScanner.new("This is a test") + s.getch + s[0].should == "T" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("This is a test") + + s.exist?(/(?<a>This)/) + s.should.matched? + s[:a].should == "This" + + s.getch + s.should.matched? + s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("This is a test") + + s.exist?(/(?<a>This)/) + s.should.matched? + s[:a].should == "This" + + s.getch + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + end + + it_behaves_like :extract_range, :getch +end diff --git a/spec/ruby/library/stringscanner/initialize_spec.rb b/spec/ruby/library/stringscanner/initialize_spec.rb new file mode 100644 index 0000000000..77f6084c1b --- /dev/null +++ b/spec/ruby/library/stringscanner/initialize_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#initialize" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "is a private method" do + StringScanner.should have_private_instance_method(:initialize) + end + + it "returns an instance of StringScanner" do + @s.should be_kind_of(StringScanner) + @s.eos?.should be_false + end + + it "converts the argument into a string using #to_str" do + m = mock(:str) + + s = "test" + m.should_receive(:to_str).and_return(s) + + scan = StringScanner.new(m) + scan.string.should == s + end + + it "accepts a fixed_anchor keyword argument" do + s = StringScanner.new("foo", fixed_anchor: true) + s.should.fixed_anchor? + end +end diff --git a/spec/ruby/library/stringscanner/inspect_spec.rb b/spec/ruby/library/stringscanner/inspect_spec.rb new file mode 100644 index 0000000000..ff6b97eb91 --- /dev/null +++ b/spec/ruby/library/stringscanner/inspect_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#inspect" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns a String object" do + @s.inspect.should be_kind_of(String) + end + + it "returns a string that represents the StringScanner object" do + @s.inspect.should == "#<StringScanner 0/14 @ \"This ...\">" + @s.scan_until(/is/) + @s.inspect.should == "#<StringScanner 4/14 \"This\" @ \" is a...\">" + @s.terminate + @s.inspect.should == "#<StringScanner fin>" + end +end diff --git a/spec/ruby/library/stringscanner/match_spec.rb b/spec/ruby/library/stringscanner/match_spec.rb new file mode 100644 index 0000000000..a27bb51d72 --- /dev/null +++ b/spec/ruby/library/stringscanner/match_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#match?" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the length of the match and the scan pointer is not advanced" do + @s.match?(/\w+/).should == 4 + @s.match?(/\w+/).should == 4 + @s.pos.should == 0 + end + + it "returns nil if there's no match" do + @s.match?(/\d+/).should == nil + @s.match?(/\s+/).should == nil + end + + it "sets the last match result" do + @s.pos = 8 + @s.match?(/a/) + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + + it "effects pre_match" do + @s.scan(/\w+/) + @s.scan(/\s/) + + @s.pre_match.should == "This" + @s.match?(/\w+/) + @s.pre_match.should == "This " + end + + describe "#[] successive call with a capture group name" do + it "returns matched substring when matching succeeded" do + @s.match?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.match?(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end +end diff --git a/spec/ruby/library/stringscanner/matched_size_spec.rb b/spec/ruby/library/stringscanner/matched_size_spec.rb new file mode 100644 index 0000000000..d9c338a07e --- /dev/null +++ b/spec/ruby/library/stringscanner/matched_size_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#matched_size" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the size of the most recent match" do + @s.check(/This/) + @s.matched_size.should == 4 + @s.matched_size.should == 4 + @s.scan(//) + @s.matched_size.should == 0 + end + + it "returns nil if there was no recent match" do + @s.matched_size.should == nil + @s.check(/\d+/) + @s.matched_size.should == nil + @s.terminate + @s.matched_size.should == nil + end +end diff --git a/spec/ruby/library/stringscanner/matched_spec.rb b/spec/ruby/library/stringscanner/matched_spec.rb new file mode 100644 index 0000000000..c020bd3eae --- /dev/null +++ b/spec/ruby/library/stringscanner/matched_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require_relative 'shared/extract_range_matched' +require 'strscan' + +describe "StringScanner#matched" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the last matched string" do + @s.match?(/\w+/) + @s.matched.should == "This" + @s.getch + @s.matched.should == "T" + @s.get_byte + @s.matched.should == "h" + end + + it "returns nil if there's no match" do + @s.match?(/\d+/) + @s.matched.should == nil + end + + it_behaves_like :extract_range_matched, :matched +end + +describe "StringScanner#matched?" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns true if the last match was successful" do + @s.match?(/\w+/) + @s.matched?.should be_true + end + + it "returns false if there's no match" do + @s.match?(/\d+/) + @s.matched?.should be_false + end +end diff --git a/spec/ruby/library/stringscanner/must_C_version_spec.rb b/spec/ruby/library/stringscanner/must_C_version_spec.rb new file mode 100644 index 0000000000..9d6edfe7b6 --- /dev/null +++ b/spec/ruby/library/stringscanner/must_C_version_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner.must_C_version" do + it "returns self" do + StringScanner.must_C_version.should == StringScanner + end +end diff --git a/spec/ruby/library/stringscanner/named_captures_spec.rb b/spec/ruby/library/stringscanner/named_captures_spec.rb new file mode 100644 index 0000000000..a68d66c216 --- /dev/null +++ b/spec/ruby/library/stringscanner/named_captures_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#named_captures" do + before do + @s = StringScanner.new('Fri Dec 12 1975 14:39') + end + + it "returns a hash of names and matched substrings for named capturing groups in a regular expression of the most recent matching" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.named_captures.should == {"wday" => "Fri", "month" => "Dec", "day" => "12"} + end + + it "returns {} if there are no named capturing groups" do + @s.exist?(/(\w+) (\w+) (\d+)/) + @s.named_captures.should == {} + end + + # https://github.com/ruby/strscan/issues/132 + ruby_bug "", ""..."3.3" do # fixed in strscan v3.0.7 + it "returns {} if there is no any matching done" do + @s.named_captures.should == {} + end + end + + it "returns nil for an optional named capturing group if it doesn't match" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\s+)?/) + @s.named_captures.should == {"wday" => "Fri", "month" => "Dec", "day" => nil} + end +end diff --git a/spec/ruby/library/stringscanner/peek_byte_spec.rb b/spec/ruby/library/stringscanner/peek_byte_spec.rb new file mode 100644 index 0000000000..88ef4a2b7c --- /dev/null +++ b/spec/ruby/library/stringscanner/peek_byte_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require 'strscan' + +version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + describe "StringScanner#peek_byte" do + it "returns a byte at the current position as an Integer" do + s = StringScanner.new('This is a test') + s.peek_byte.should == 84 + end + + it "returns nil at the end of the string" do + s = StringScanner.new('a') + s.getch # skip one + s.pos.should == 1 + s.peek_byte.should == nil + end + + it "is not multi-byte character sensitive" do + s = StringScanner.new("∂") # "∂".bytes => [226, 136, 130] + s.peek_byte.should == 226 + s.pos = 1 + s.peek_byte.should == 136 + s.pos = 2 + s.peek_byte.should == 130 + end + + it "doesn't change current position" do + s = StringScanner.new('This is a test') + + s.pos.should == 0 + s.peek_byte.should == 84 + s.pos.should == 0 + end + end +end diff --git a/spec/ruby/library/stringscanner/peek_spec.rb b/spec/ruby/library/stringscanner/peek_spec.rb new file mode 100644 index 0000000000..d490abecf9 --- /dev/null +++ b/spec/ruby/library/stringscanner/peek_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#peek" do + before :each do + @s = StringScanner.new('This is a test') + end + + it "returns at most the specified number of bytes from the current position" do + @s.peek(4).should == "This" + @s.pos.should == 0 + @s.pos = 5 + @s.peek(2).should == "is" + @s.peek(1000).should == "is a test" + + s = StringScanner.new("été") + s.peek(2).should == "é" + end + + it "returns an empty string when the passed argument is zero" do + @s.peek(0).should == "" + end + + it "raises a ArgumentError when the passed argument is negative" do + -> { @s.peek(-2) }.should raise_error(ArgumentError) + end + + it "raises a RangeError when the passed argument is a Bignum" do + -> { @s.peek(bignum_value) }.should raise_error(RangeError) + end + + it "returns an instance of String when passed a String subclass" do + cls = Class.new(String) + sub = cls.new("abc") + + s = StringScanner.new(sub) + + ch = s.peek(1) + ch.should_not be_kind_of(cls) + ch.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/stringscanner/pointer_spec.rb b/spec/ruby/library/stringscanner/pointer_spec.rb new file mode 100644 index 0000000000..bc0c0c50b7 --- /dev/null +++ b/spec/ruby/library/stringscanner/pointer_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/pos' +require 'strscan' + +describe "StringScanner#pointer" do + it_behaves_like :strscan_pos, :pointer +end + +describe "StringScanner#pointer=" do + it_behaves_like :strscan_pos_set, :pointer= +end diff --git a/spec/ruby/library/stringscanner/pos_spec.rb b/spec/ruby/library/stringscanner/pos_spec.rb new file mode 100644 index 0000000000..275fecf0f3 --- /dev/null +++ b/spec/ruby/library/stringscanner/pos_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/pos' +require 'strscan' + +describe "StringScanner#pos" do + it_behaves_like :strscan_pos, :pos +end + +describe "StringScanner#pos=" do + it_behaves_like :strscan_pos_set, :pos= +end diff --git a/spec/ruby/library/stringscanner/post_match_spec.rb b/spec/ruby/library/stringscanner/post_match_spec.rb new file mode 100644 index 0000000000..720dc3530d --- /dev/null +++ b/spec/ruby/library/stringscanner/post_match_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require_relative 'shared/extract_range_matched' +require 'strscan' + +describe "StringScanner#post_match" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the post-match (in the regular expression sense) of the last scan" do + @s.post_match.should == nil + @s.scan(/\w+\s/) + @s.post_match.should == "is a test" + @s.getch + @s.post_match.should == "s a test" + @s.get_byte + @s.post_match.should == " a test" + @s.get_byte + @s.post_match.should == "a test" + end + + it "returns nil if there's no match" do + @s.scan(/\s+/) + @s.post_match.should == nil + end + + it_behaves_like :extract_range_matched, :post_match +end diff --git a/spec/ruby/library/stringscanner/pre_match_spec.rb b/spec/ruby/library/stringscanner/pre_match_spec.rb new file mode 100644 index 0000000000..1c2c511d5c --- /dev/null +++ b/spec/ruby/library/stringscanner/pre_match_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require_relative 'shared/extract_range_matched' +require 'strscan' + +describe "StringScanner#pre_match" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the pre-match (in the regular expression sense) of the last scan" do + @s.pre_match.should == nil + @s.scan(/\w+\s/) + @s.pre_match.should == "" + @s.getch + @s.pre_match.should == "This " + @s.get_byte + @s.pre_match.should == "This i" + @s.get_byte + @s.pre_match.should == "This is" + end + + it "returns nil if there's no match" do + @s.scan(/\s+/) + @s.pre_match.should == nil + end + + it "is more than just the data from the last match" do + @s.scan(/\w+/) + @s.scan_until(/a te/) + @s.pre_match.should == "This is " + end + + it "is not changed when the scanner's position changes" do + @s.scan_until(/\s+/) + @s.pre_match.should == "This" + @s.pos -= 1 + @s.pre_match.should == "This" + end + + it_behaves_like :extract_range_matched, :pre_match +end diff --git a/spec/ruby/library/stringscanner/reset_spec.rb b/spec/ruby/library/stringscanner/reset_spec.rb new file mode 100644 index 0000000000..a10ca2d838 --- /dev/null +++ b/spec/ruby/library/stringscanner/reset_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#reset" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "reset the scan pointer and clear matching data" do + @s.scan(/This/) + @s.reset + @s.pos.should == 0 + @s.matched.should == nil + end +end diff --git a/spec/ruby/library/stringscanner/rest_size_spec.rb b/spec/ruby/library/stringscanner/rest_size_spec.rb new file mode 100644 index 0000000000..a5e971631a --- /dev/null +++ b/spec/ruby/library/stringscanner/rest_size_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#rest_size" do + before :each do + @s = StringScanner.new('This is a test') + end + + it "returns the length of the rest of the string" do + @s.rest_size.should == 14 + @s.scan(/This/) + @s.rest_size.should == 10 + @s.terminate + @s.rest_size.should == 0 + end + + it "is equivalent to rest.size" do + @s.scan(/This/) + @s.rest_size.should == @s.rest.size + end +end diff --git a/spec/ruby/library/stringscanner/rest_spec.rb b/spec/ruby/library/stringscanner/rest_spec.rb new file mode 100644 index 0000000000..67072f880d --- /dev/null +++ b/spec/ruby/library/stringscanner/rest_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'shared/extract_range_matched' +require 'strscan' + +describe "StringScanner#rest" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the rest of the string" do + @s.scan(/This\s+/) + @s.rest.should == "is a test" + end + + it "returns self in the reset position" do + @s.reset + @s.rest.should == @s.string + end + + it "returns an empty string in the terminate position" do + @s.terminate + @s.rest.should == "" + end + + it_behaves_like :extract_range_matched, :rest + +end + +describe "StringScanner#rest?" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns true if there is more data in the string" do + @s.rest?.should be_true + @s.scan(/This/) + @s.rest?.should be_true + end + + it "returns false if there is no more data in the string" do + @s.terminate + @s.rest?.should be_false + end + + it "is the opposite of eos?" do + @s.rest?.should_not == @s.eos? + end +end diff --git a/spec/ruby/library/stringscanner/scan_byte_spec.rb b/spec/ruby/library/stringscanner/scan_byte_spec.rb new file mode 100644 index 0000000000..aa2decc8f7 --- /dev/null +++ b/spec/ruby/library/stringscanner/scan_byte_spec.rb @@ -0,0 +1,98 @@ +require_relative '../../spec_helper' +require 'strscan' + +version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + describe "StringScanner#scan_byte" do + it "scans one byte and returns it as on Integer" do + s = StringScanner.new('abc') # "abc".bytes => [97, 98, 99] + s.scan_byte.should == 97 + s.scan_byte.should == 98 + s.scan_byte.should == 99 + end + + it "is not multi-byte character sensitive" do + s = StringScanner.new("あ") # "あ".bytes => [227, 129, 130] + s.scan_byte.should == 227 + s.scan_byte.should == 129 + s.scan_byte.should == 130 + end + + it "returns nil at the end of the string" do + s = StringScanner.new('a') + s.scan_byte # skip one + s.scan_byte.should == nil + s.pos.should == 1 + end + + it "changes current position" do + s = StringScanner.new('abc') + s.pos.should == 0 + s.scan_byte + s.pos.should == 1 + end + + it "sets the last match result" do + s = StringScanner.new('abc') + s.pos = 1 + s.scan_byte + + s.pre_match.should == "a" + s.matched.should == "b" + s.post_match.should == "c" + end + + describe "#[] successive call with a capture group name" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil" do + s = StringScanner.new("abc") + s.scan_byte + s.should.matched? + s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError" do + s = StringScanner.new("abc") + s.scan_byte + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + + it "returns a matching character when given Integer index" do + s = StringScanner.new("This is a test") + s.scan_byte + s[0].should == "T" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("abc") + + s.exist?(/(?<a>a)/) + s.should.matched? + s[:a].should == "a" + + s.scan_byte + s.should.matched? + s[:a].should == nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("abc") + + s.exist?(/(?<a>a)/) + s.should.matched? + s[:a].should == "a" + + s.scan_byte + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/scan_full_spec.rb b/spec/ruby/library/stringscanner/scan_full_spec.rb new file mode 100644 index 0000000000..967313f5ca --- /dev/null +++ b/spec/ruby/library/stringscanner/scan_full_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#scan_full" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the number of bytes advanced" do + orig_pos = @s.pos + @s.scan_full(/This/, false, false).should == 4 + @s.pos.should == orig_pos + end + + it "returns the number of bytes advanced and advances the scan pointer if the second argument is true" do + @s.scan_full(/This/, true, false).should == 4 + @s.pos.should == 4 + end + + it "returns the matched string if the third argument is true" do + orig_pos = @s.pos + @s.scan_full(/This/, false, true).should == "This" + @s.pos.should == orig_pos + end + + it "returns the matched string if the third argument is true and advances the scan pointer if the second argument is true" do + @s.scan_full(/This/, true, true).should == "This" + @s.pos.should == 4 + end + + describe "#[] successive call with a capture group name" do + it "returns matched substring when matching succeeded" do + @s.scan_full(/(?<a>This)/, false, false) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.scan_full(/(?<a>2008)/, false, false) + @s.should_not.matched? + @s[:a].should be_nil + end + end +end diff --git a/spec/ruby/library/stringscanner/scan_integer_spec.rb b/spec/ruby/library/stringscanner/scan_integer_spec.rb new file mode 100644 index 0000000000..fe0d26f404 --- /dev/null +++ b/spec/ruby/library/stringscanner/scan_integer_spec.rb @@ -0,0 +1,157 @@ +require_relative '../../spec_helper' +require 'strscan' + +version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + describe "StringScanner#scan_integer" do + it "returns the matched Integer literal starting from the current position" do + s = StringScanner.new("42") + s.scan_integer.should == 42 + end + + it "returns nil when no Integer literal matched starting from the current position" do + s = StringScanner.new("a42") + s.scan_integer.should == nil + end + + it "supports a sign +/-" do + StringScanner.new("+42").scan_integer.should == 42 + StringScanner.new("-42").scan_integer.should == -42 + end + + it "changes the current position" do + s = StringScanner.new("42abc") + s.scan_integer + s.pos.should == 2 + end + + # https://github.com/ruby/strscan/issues/130 + ruby_bug "", "3.4"..."4.0" do # introduced in strscan v3.1.1 + it "sets the last match result" do + s = StringScanner.new("42abc") + s.scan_integer + + s.should.matched? + s.matched.should == "42" + s.pre_match.should == "" + s.post_match.should == "abc" + s.matched_size.should == 2 + end + end + + it "raises Encoding::CompatibilityError when a scanned string not is ASCII-compatible encoding" do + string = "42".encode(Encoding::UTF_16BE) + s = StringScanner.new(string) + + -> { + s.scan_integer + }.should raise_error(Encoding::CompatibilityError, 'ASCII incompatible encoding: UTF-16BE') + end + + context "given base" do + it "supports base: 10" do + s = StringScanner.new("42") + s.scan_integer(base: 10).should == 42 + end + + it "supports base: 16" do + StringScanner.new("0xff").scan_integer(base: 16).should == 0xff + StringScanner.new("-0xff").scan_integer(base: 16).should == -0xff + StringScanner.new("0xFF").scan_integer(base: 16).should == 0xff + StringScanner.new("-0xFF").scan_integer(base: 16).should == -0xff + StringScanner.new("ff").scan_integer(base: 16).should == 0xff + StringScanner.new("-ff").scan_integer(base: 16).should == -0xff + end + + it "raises ArgumentError when passed not supported base" do + -> { + StringScanner.new("42").scan_integer(base: 5) + }.should raise_error(ArgumentError, "Unsupported integer base: 5, expected 10 or 16") + end + + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "does not match '0x' prefix on its own" do + StringScanner.new("0x").scan_integer(base: 16).should == nil + StringScanner.new("-0x").scan_integer(base: 16).should == nil + StringScanner.new("+0x").scan_integer(base: 16).should == nil + end + end + + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "matches '0' in a '0x' that is followed by non-hex characters" do + StringScanner.new("0x!@#").scan_integer(base: 16).should == 0 + StringScanner.new("-0x!@#").scan_integer(base: 16).should == 0 + StringScanner.new("+0x!@#").scan_integer(base: 16).should == 0 + end + + it "matches '0' in a '0x' located in the end of a string" do + StringScanner.new("0x").scan_integer(base: 16).should == 0 + StringScanner.new("-0x").scan_integer(base: 16).should == 0 + StringScanner.new("+0x").scan_integer(base: 16).should == 0 + end + end + end + end + + describe "#[] successive call with a capture group name" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil substring when matching succeeded" do + s = StringScanner.new("42") + s.scan_integer + s.should.matched? + s[:a].should == nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError when matching succeeded" do + s = StringScanner.new("42") + s.scan_integer + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + s = StringScanner.new("a42") + s.scan_integer + s.should_not.matched? + s[:a].should be_nil + end + + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "returns a matching substring when given Integer index" do + s = StringScanner.new("42") + s.scan_integer + s[0].should == "42" + end + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "does not ignore the previous matching with Regexp" do + s = StringScanner.new("42") + + s.exist?(/(?<a>42)/) + s.should.matched? + s[:a].should == "42" + + s.scan_integer + s.should.matched? + s[:a].should == "42" + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "ignores the previous matching with Regexp" do + s = StringScanner.new("42") + + s.exist?(/(?<a>42)/) + s.should.matched? + s[:a].should == "42" + + s.scan_integer + s.should.matched? + -> { s[:a] }.should raise_error(IndexError) + end + end + end +end diff --git a/spec/ruby/library/stringscanner/scan_spec.rb b/spec/ruby/library/stringscanner/scan_spec.rb new file mode 100644 index 0000000000..088c3419fb --- /dev/null +++ b/spec/ruby/library/stringscanner/scan_spec.rb @@ -0,0 +1,101 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#scan" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the matched string" do + @s.scan(/\w+/).should == "This" + @s.scan(/.../).should == " is" + @s.scan(//).should == "" + @s.scan(/\s+/).should == " " + end + + it "treats ^ as matching from the beginning of the current position" do + @s.scan(/\w+/).should == "This" + @s.scan(/^\d/).should be_nil + @s.scan(/^\s/).should == " " + end + + it "treats ^ as matching from the beginning of the current position when it's not the first character in the regexp" do + @s.scan(/\w+/).should == "This" + @s.scan(/( is not|^ is a)/).should == " is a" + end + + it "treats \\A as matching from the beginning of the current position" do + @s.scan(/\w+/).should == "This" + @s.scan(/\A\d/).should be_nil + @s.scan(/\A\s/).should == " " + end + + it "treats \\A as matching from the beginning of the current position when it's not the first character in the regexp" do + @s.scan(/\w+/).should == "This" + @s.scan(/( is not|\A is a)/).should == " is a" + end + + it "returns nil if there's no match" do + @s.scan(/\d/).should == nil + end + + it "returns nil when there is no more to scan" do + @s.scan(/[\w\s]+/).should == "This is a test" + @s.scan(/\w+/).should be_nil + end + + it "returns an empty string when the pattern matches empty" do + @s.scan(/.*/).should == "This is a test" + @s.scan(/.*/).should == "" + @s.scan(/./).should be_nil + end + + it "treats String as the pattern itself" do + @s.scan("this").should be_nil + @s.scan("This").should == "This" + end + + it "raises a TypeError if pattern isn't a Regexp nor String" do + -> { @s.scan(5) }.should raise_error(TypeError) + -> { @s.scan(:test) }.should raise_error(TypeError) + -> { @s.scan(mock('x')) }.should raise_error(TypeError) + end + + describe "#[] successive call with a capture group name" do + it "returns matched substring when matching succeeded" do + @s.scan(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.scan(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end +end + +describe "StringScanner#scan with fixed_anchor: true" do + before :each do + @s = StringScanner.new("This\nis\na\ntest", fixed_anchor: true) + end + + it "returns the matched string" do + @s.scan(/\w+/).should == "This" + @s.scan(/.../m).should == "\nis" + @s.scan(//).should == "" + @s.scan(/\s+/).should == "\n" + end + + it "treats ^ as matching from the beginning of line" do + @s.scan(/\w+\n/).should == "This\n" + @s.scan(/^\w/).should == "i" + @s.scan(/^\w/).should be_nil + end + + it "treats \\A as matching from the beginning of string" do + @s.scan(/\A\w/).should == "T" + @s.scan(/\A\w/).should be_nil + end +end diff --git a/spec/ruby/library/stringscanner/scan_until_spec.rb b/spec/ruby/library/stringscanner/scan_until_spec.rb new file mode 100644 index 0000000000..610060d6f1 --- /dev/null +++ b/spec/ruby/library/stringscanner/scan_until_spec.rb @@ -0,0 +1,135 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#scan_until" do + before do + @s = StringScanner.new("This is a test") + end + + it "returns the substring up to and including the end of the match" do + @s.scan_until(/a/).should == "This is a" + end + + it "sets the last match result" do + @s.scan_until(/a/) + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + + it "returns nil if there's no match" do + @s.scan_until(/\d/).should == nil + end + + it "can match anchors properly" do + @s.scan(/T/) + @s.scan_until(/^h/).should == "h" + end + + version_is StringScanner::Version, ""..."3.1.1" do # ruby_version_is ""..."3.4" + it "raises TypeError if given a String" do + -> { + @s.scan_until('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + it "searches a substring in the rest part of a string if given a String" do + @s.scan_until("a").should == "This is a" + end + + # https://github.com/ruby/strscan/issues/131 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" + it "sets the last match result if given a String" do + @s.scan_until("a") + + @s.pre_match.should == "" + @s.matched.should == "This is a" + @s.post_match.should == " test" + end + end + + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "sets the last match result if given a String" do + @s.scan_until("a") + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + end + end + + describe "#[] successive call with a capture group name" do + context "when #scan_until was called with a Regexp pattern" do + it "returns matched substring when matching succeeded" do + @s.scan_until(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.scan_until(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + context "when #scan_until was called with a String pattern" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil when matching succeeded" do + @s.scan_until("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError when matching succeeded" do + @s.scan_until("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + @s.scan_until("2008") + @s.should_not.matched? + @s[:a].should be_nil + end + + it "returns a matching substring when given Integer index" do + @s.scan_until("This") + @s[0].should == "This" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.scan_until("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.scan_until("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/search_full_spec.rb b/spec/ruby/library/stringscanner/search_full_spec.rb new file mode 100644 index 0000000000..197adfda4d --- /dev/null +++ b/spec/ruby/library/stringscanner/search_full_spec.rb @@ -0,0 +1,133 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#search_full" do + before do + @s = StringScanner.new("This is a test") + end + + it "returns the number of bytes advanced" do + orig_pos = @s.pos + @s.search_full(/This/, false, false).should == 4 + @s.pos.should == orig_pos + end + + it "returns the number of bytes advanced and advances the scan pointer if the second argument is true" do + @s.search_full(/This/, true, false).should == 4 + @s.pos.should == 4 + end + + it "returns the matched string if the third argument is true" do + orig_pos = @s.pos + @s.search_full(/This/, false, true).should == "This" + @s.pos.should == orig_pos + end + + it "returns the matched string if the third argument is true and advances the scan pointer if the second argument is true" do + @s.search_full(/This/, true, true).should == "This" + @s.pos.should == 4 + end + + it "sets the last match result" do + @s.search_full(/is a/, false, false) + + @s.pre_match.should == "This " + @s.matched.should == "is a" + @s.post_match.should == " test" + end + + version_is StringScanner::Version, ""..."3.1.1" do # ruby_version_is ""..."3.4" + it "raises TypeError if given a String" do + -> { + @s.search_full('T', true, true) + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + it "searches a substring in the rest part of a string if given a String" do + @s.search_full("is a", false, false).should == 9 + end + + # https://github.com/ruby/strscan/issues/131 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" + it "sets the last match result if given a String" do + @s.search_full("is a", false, false) + + @s.pre_match.should == "" + @s.matched.should == "This is a" + @s.post_match.should == " test" + end + end + + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "sets the last match result if given a String" do + @s.search_full("is a", false, false) + + @s.pre_match.should == "This " + @s.matched.should == "is a" + @s.post_match.should == " test" + end + end + end + + describe "#[] successive call with a capture group name" do + context "when #search_full was called with a Regexp pattern" do + it "returns matched substring when matching succeeded" do + @s.search_full(/(?<a>This)/, false, false) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.search_full(/(?<a>2008)/, false, false) + @s.should_not.matched? + @s[:a].should be_nil + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + context "when #search_full was called with a String pattern" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil when matching succeeded" do + @s.search_full("This", false, false) + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError when matching succeeded" do + @s.search_full("This", false, false) + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + @s.search_full("2008", false, false) + @s.should_not.matched? + @s[:a].should be_nil + end + + it "returns a matching substring when given Integer index" do + @s.search_full("This", false, false) + @s[0].should == "This" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.search_full("This", false, false) + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/shared/bol.rb b/spec/ruby/library/stringscanner/shared/bol.rb new file mode 100644 index 0000000000..ebcdd7938f --- /dev/null +++ b/spec/ruby/library/stringscanner/shared/bol.rb @@ -0,0 +1,25 @@ +describe :strscan_bol, shared: true do + it "returns true if the scan pointer is at the beginning of the line, false otherwise" do + s = StringScanner.new("This is a test") + s.send(@method).should be_true + s.scan(/This/) + s.send(@method).should be_false + s.terminate + s.send(@method).should be_false + + s = StringScanner.new("hello\nworld") + s.bol?.should be_true + s.scan(/\w+/) + s.bol?.should be_false + s.scan(/\n/) + s.bol?.should be_true + s.unscan + s.bol?.should be_false + end + + it "returns true if the scan pointer is at the end of the line of an empty string." do + s = StringScanner.new('') + s.terminate + s.send(@method).should be_true + end +end diff --git a/spec/ruby/library/stringscanner/shared/concat.rb b/spec/ruby/library/stringscanner/shared/concat.rb new file mode 100644 index 0000000000..1dbae11f7c --- /dev/null +++ b/spec/ruby/library/stringscanner/shared/concat.rb @@ -0,0 +1,30 @@ +describe :strscan_concat, shared: true do + it "concatenates the given argument to self and returns self" do + s = StringScanner.new(+"hello ") + s.send(@method, 'world').should == s + s.string.should == "hello world" + s.eos?.should be_false + end + + it "raises a TypeError if the given argument can't be converted to a String" do + -> { StringScanner.new('hello').send(@method, :world) }.should raise_error(TypeError) + -> { StringScanner.new('hello').send(@method, mock('x')) }.should raise_error(TypeError) + end +end + +describe :strscan_concat_fixnum, shared: true do + it "raises a TypeError" do + a = StringScanner.new("hello world") + -> { a.send(@method, 333) }.should raise_error(TypeError) + b = StringScanner.new("") + -> { b.send(@method, (256 * 3 + 64)) }.should raise_error(TypeError) + -> { b.send(@method, -200) }.should raise_error(TypeError) + end + + it "doesn't call to_int on the argument" do + x = mock('x') + x.should_not_receive(:to_int) + + -> { StringScanner.new("").send(@method, x) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/library/stringscanner/shared/extract_range.rb b/spec/ruby/library/stringscanner/shared/extract_range.rb new file mode 100644 index 0000000000..e7404fd0cb --- /dev/null +++ b/spec/ruby/library/stringscanner/shared/extract_range.rb @@ -0,0 +1,11 @@ +describe :extract_range, shared: true do + it "returns an instance of String when passed a String subclass" do + cls = Class.new(String) + sub = cls.new("abc") + + s = StringScanner.new(sub) + ch = s.send(@method) + ch.should_not be_kind_of(cls) + ch.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/stringscanner/shared/extract_range_matched.rb b/spec/ruby/library/stringscanner/shared/extract_range_matched.rb new file mode 100644 index 0000000000..070a132812 --- /dev/null +++ b/spec/ruby/library/stringscanner/shared/extract_range_matched.rb @@ -0,0 +1,13 @@ +describe :extract_range_matched, shared: true do + it "returns an instance of String when passed a String subclass" do + cls = Class.new(String) + sub = cls.new("abc") + + s = StringScanner.new(sub) + s.scan(/\w{1}/) + + ch = s.send(@method) + ch.should_not be_kind_of(cls) + ch.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/stringscanner/shared/pos.rb b/spec/ruby/library/stringscanner/shared/pos.rb new file mode 100644 index 0000000000..eea7ead6b5 --- /dev/null +++ b/spec/ruby/library/stringscanner/shared/pos.rb @@ -0,0 +1,59 @@ +describe :strscan_pos, shared: true do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the position of the scan pointer" do + @s.send(@method).should == 0 + @s.scan_until(/This is/) + @s.send(@method).should == 7 + @s.get_byte + @s.send(@method).should == 8 + @s.terminate + @s.send(@method).should == 14 + end + + it "returns 0 in the reset position" do + @s.reset + @s.send(@method).should == 0 + end + + it "returns the length of the string in the terminate position" do + @s.terminate + @s.send(@method).should == @s.string.length + end + + it "is not multi-byte character sensitive" do + s = StringScanner.new("abcädeföghi") + + s.scan_until(/ö/) + s.pos.should == 10 + end +end + +describe :strscan_pos_set, shared: true do + before :each do + @s = StringScanner.new("This is a test") + end + + it "modify the scan pointer" do + @s.send(@method, 5) + @s.rest.should == "is a test" + end + + it "positions from the end if the argument is negative" do + @s.send(@method, -2) + @s.rest.should == "st" + @s.pos.should == 12 + end + + it "raises a RangeError if position too far backward" do + -> { + @s.send(@method, -20) + }.should raise_error(RangeError) + end + + it "raises a RangeError when the passed argument is out of range" do + -> { @s.send(@method, 20) }.should raise_error(RangeError) + end +end diff --git a/spec/ruby/library/stringscanner/size_spec.rb b/spec/ruby/library/stringscanner/size_spec.rb new file mode 100644 index 0000000000..3e475489e3 --- /dev/null +++ b/spec/ruby/library/stringscanner/size_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#size" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns the number of captures groups of the last match" do + @s.scan(/(.)(.)(.)/) + @s.size.should == 4 + end + + it "returns nil if there is no last match" do + @s.size.should == nil + end +end diff --git a/spec/ruby/library/stringscanner/skip_spec.rb b/spec/ruby/library/stringscanner/skip_spec.rb new file mode 100644 index 0000000000..12f5b7781c --- /dev/null +++ b/spec/ruby/library/stringscanner/skip_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#skip" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "returns length of the match" do + @s.skip(/\w+/).should == 4 + @s.skip(/\s+\w+/).should == 3 + end + + it "returns nil if there's no match" do + @s.skip(/\s+/).should == nil + @s.skip(/\d+/).should == nil + end + + describe "#[] successive call with a capture group name" do + it "returns matched substring when matching succeeded" do + @s.skip(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.skip(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end +end diff --git a/spec/ruby/library/stringscanner/skip_until_spec.rb b/spec/ruby/library/stringscanner/skip_until_spec.rb new file mode 100644 index 0000000000..5d73d8f0b9 --- /dev/null +++ b/spec/ruby/library/stringscanner/skip_until_spec.rb @@ -0,0 +1,132 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#skip_until" do + before do + @s = StringScanner.new("This is a test") + end + + it "returns the number of bytes advanced and advances the scan pointer until pattern is matched and consumed" do + @s.skip_until(/a/).should == 9 + @s.pos.should == 9 + end + + it "sets the last match result" do + @s.skip_until(/a/) + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + + it "returns nil if no match was found" do + @s.skip_until(/d+/).should == nil + end + + version_is StringScanner::Version, ""..."3.1.1" do # ruby_version_is ""..."3.4" + it "raises TypeError if given a String" do + -> { + @s.skip_until('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + it "searches a substring in the rest part of a string if given a String" do + @s.skip_until("a").should == 9 + @s.pos.should == 9 + end + + # https://github.com/ruby/strscan/issues/131 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.1" + it "sets the last match result if given a String" do + @s.skip_until("a") + + @s.pre_match.should == "" + @s.matched.should == "This is a" + @s.post_match.should == " test" + end + end + + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "sets the last match result if given a String" do + @s.skip_until("a") + + @s.pre_match.should == "This is " + @s.matched.should == "a" + @s.post_match.should == " test" + end + end + end + + describe "#[] successive call with a capture group name" do + context "when #scan_until was called with a Regexp pattern" do + it "returns matched substring when matching succeeded" do + @s.skip_until(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + end + + it "returns nil when matching failed" do + @s.skip_until(/(?<a>2008)/) + @s.should_not.matched? + @s[:a].should be_nil + end + end + + version_is StringScanner::Version, "3.1.1" do # ruby_version_is "3.4" + context "when #skip_until was called with a String pattern" do + # https://github.com/ruby/strscan/issues/139 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "returns nil when matching succeeded" do + @s.skip_until("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4.3" + it "raises IndexError when matching succeeded" do + @s.skip_until("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + + it "returns nil when matching failed" do + @s.skip_until("2008") + @s.should_not.matched? + @s[:a].should be_nil + end + + it "returns a matching substring when given Integer index" do + @s.skip_until("This") + @s[0].should == "This" + end + + # https://github.com/ruby/strscan/issues/135 + version_is StringScanner::Version, "3.1.1"..."3.1.3" do # ruby_version_is "3.4.0"..."3.4.3" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.skip_until("This") + @s.should.matched? + @s[:a].should be_nil + end + end + version_is StringScanner::Version, "3.1.3" do # ruby_version_is "3.4" + it "ignores the previous matching with Regexp" do + @s.exist?(/(?<a>This)/) + @s.should.matched? + @s[:a].should == "This" + + @s.skip_until("This") + @s.should.matched? + -> { @s[:a] }.should raise_error(IndexError) + end + end + end + end + end +end diff --git a/spec/ruby/library/stringscanner/string_spec.rb b/spec/ruby/library/stringscanner/string_spec.rb new file mode 100644 index 0000000000..cba6bd51dd --- /dev/null +++ b/spec/ruby/library/stringscanner/string_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#string" do + before :each do + @string = +"This is a test" + @s = StringScanner.new(@string) + end + + it "returns the string being scanned" do + @s.string.should == "This is a test" + @s << " case" + @s.string.should == "This is a test case" + end + + it "returns the identical object passed in" do + @s.string.equal?(@string).should be_true + end +end + +describe "StringScanner#string=" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "changes the string being scanned to the argument and resets the scanner" do + @s.string = "Hello world" + @s.string.should == "Hello world" + end + + it "converts the argument into a string using #to_str" do + m = mock(:str) + + s = "test" + m.should_receive(:to_str).and_return(s) + + @s.string = m + @s.string.should == s + end +end diff --git a/spec/ruby/library/stringscanner/terminate_spec.rb b/spec/ruby/library/stringscanner/terminate_spec.rb new file mode 100644 index 0000000000..3cff5c010c --- /dev/null +++ b/spec/ruby/library/stringscanner/terminate_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#terminate" do + it "set the scan pointer to the end of the string and clear matching data." do + s = StringScanner.new('This is a test') + s.terminate + s.should_not.bol? + s.should.eos? + end +end diff --git a/spec/ruby/library/stringscanner/unscan_spec.rb b/spec/ruby/library/stringscanner/unscan_spec.rb new file mode 100644 index 0000000000..b7b4876b8a --- /dev/null +++ b/spec/ruby/library/stringscanner/unscan_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#unscan" do + before :each do + @s = StringScanner.new("This is a test") + end + + it "set the scan pointer to the previous position" do + @s.scan(/This/) + @s.unscan + @s.matched.should == nil + @s.pos.should == 0 + end + + it "remember only one previous position" do + @s.scan(/This/) + pos = @s.pos + @s.scan(/ is/) + @s.unscan + @s.pos.should == pos + end + + it "raises a StringScanner::Error when the previous match had failed" do + -> { @s.unscan }.should raise_error(StringScanner::Error) + -> { @s.scan(/\d/); @s.unscan }.should raise_error(StringScanner::Error) + end +end diff --git a/spec/ruby/library/stringscanner/values_at_spec.rb b/spec/ruby/library/stringscanner/values_at_spec.rb new file mode 100644 index 0000000000..14d4a5f6a7 --- /dev/null +++ b/spec/ruby/library/stringscanner/values_at_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../spec_helper' +require 'strscan' + +describe "StringScanner#captures" do + before do + @s = StringScanner.new('Fri Dec 12 1975 14:39') + end + + context "when passed a list of Integers" do + it "returns an array containing each value given by one of integers" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.values_at(0, 1, 2, 3).should == ["Fri Dec 12", "Fri", "Dec", "12"] + end + + it "returns nil value for any integer that is out of range" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.values_at(4).should == [nil] + @s.values_at(-5).should == [nil] + end + end + + context "when passed names" do + it 'slices captures with the given names' do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.values_at(:wday, :month, :day).should == ["Fri", "Dec", "12"] + end + + it 'slices captures with the given String names' do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.values_at("wday", "month", "day").should == ["Fri", "Dec", "12"] + end + + it "raises IndexError when given unknown name" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + + -> { + @s.values_at("foo") + }.should raise_error(IndexError, "undefined group name reference: foo") + end + end + + it 'supports mixing of names and indices' do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.values_at(1, "wday", 2, "month", 3, "day").should == ["Fri", "Fri", "Dec", "Dec", "12", "12"] + end + + it "returns a new empty Array if no arguments given" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + @s.values_at().should == [] + end + + it "fails when passed arguments of unsupported types" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+)/) + + -> { + @s.values_at([]) + }.should raise_error(TypeError, "no implicit conversion of Array into Integer") + end + + it "returns nil if the most recent matching fails" do + @s.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\s+)/) + @s.values_at(1, 2, 3).should == nil + end + + it "returns nil if there is no any matching done" do + @s.values_at(1, 2, 3).should == nil + end +end diff --git a/spec/ruby/library/syslog/alert_spec.rb b/spec/ruby/library/syslog/alert_spec.rb new file mode 100644 index 0000000000..edff789dc9 --- /dev/null +++ b/spec/ruby/library/syslog/alert_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.alert" do + it_behaves_like :syslog_log, :alert + end +end diff --git a/spec/ruby/library/syslog/close_spec.rb b/spec/ruby/library/syslog/close_spec.rb new file mode 100644 index 0000000000..8c3b67c05b --- /dev/null +++ b/spec/ruby/library/syslog/close_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.close" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "closes the log" do + Syslog.opened?.should be_false + Syslog.open + Syslog.opened?.should be_true + Syslog.close + Syslog.opened?.should be_false + end + + it "raises a RuntimeError if the log's already closed" do + -> { Syslog.close }.should raise_error(RuntimeError) + end + + it "it does not work inside blocks" do + -> { + Syslog.open { |s| s.close } + }.should raise_error(RuntimeError) + Syslog.should_not.opened? + end + + it "sets the identity to nil" do + Syslog.open("rubyspec") + Syslog.ident.should == "rubyspec" + Syslog.close + Syslog.ident.should be_nil + end + + it "sets the options to nil" do + Syslog.open("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + Syslog.options.should == nil + end + + it "sets the facility to nil" do + Syslog.open + Syslog.facility.should == 8 + Syslog.close + Syslog.facility.should == nil + end + end + end +end diff --git a/spec/ruby/library/syslog/constants_spec.rb b/spec/ruby/library/syslog/constants_spec.rb new file mode 100644 index 0000000000..fc9db47dd8 --- /dev/null +++ b/spec/ruby/library/syslog/constants_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog::Constants" do + platform_is_not :windows, :aix do + before :all do + @constants = %w(LOG_AUTHPRIV LOG_USER LOG_LOCAL2 LOG_NOTICE LOG_NDELAY + LOG_SYSLOG LOG_ALERT LOG_FTP LOG_LOCAL5 LOG_ERR LOG_AUTH + LOG_LOCAL1 LOG_ODELAY LOG_NEWS LOG_DAEMON LOG_LOCAL4 + LOG_CRIT LOG_INFO LOG_PERROR LOG_LOCAL0 LOG_CONS LOG_LPR + LOG_LOCAL7 LOG_WARNING LOG_CRON LOG_LOCAL3 LOG_EMERG + LOG_NOWAIT LOG_UUCP LOG_PID LOG_KERN LOG_MAIL LOG_LOCAL6 + LOG_DEBUG) + end + + it "includes the Syslog constants" do + @constants.each do |c| + Syslog::Constants.should have_constant(c) + end + end + end + + # The masks are defined in <syslog.h> + + describe "Syslog::Constants.LOG_MASK" do + it "returns the mask value for a priority" do + Syslog::Constants.LOG_MASK(Syslog::LOG_DEBUG).should == 128 + Syslog::Constants.LOG_MASK(Syslog::LOG_WARNING).should == 16 + end + end + + describe "Syslog::Constants.LOG_UPTO" do + it "returns a mask for the priorities up to a given argument" do + Syslog::Constants.LOG_UPTO(Syslog::LOG_ALERT).should == 3 + Syslog::Constants.LOG_UPTO(Syslog::LOG_DEBUG).should == 255 + end + end + end +end diff --git a/spec/ruby/library/syslog/crit_spec.rb b/spec/ruby/library/syslog/crit_spec.rb new file mode 100644 index 0000000000..5d3904f719 --- /dev/null +++ b/spec/ruby/library/syslog/crit_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.crit" do + it_behaves_like :syslog_log, :crit + end +end diff --git a/spec/ruby/library/syslog/debug_spec.rb b/spec/ruby/library/syslog/debug_spec.rb new file mode 100644 index 0000000000..d03e8a88c9 --- /dev/null +++ b/spec/ruby/library/syslog/debug_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.debug" do + it_behaves_like :syslog_log, :debug + end +end diff --git a/spec/ruby/library/syslog/emerg_spec.rb b/spec/ruby/library/syslog/emerg_spec.rb new file mode 100644 index 0000000000..2ab4d60291 --- /dev/null +++ b/spec/ruby/library/syslog/emerg_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.emerg" do + # Some way needs do be found to prevent this spec + # from causing output on all open terminals. If this + # is not possible, this spec may need a special guard + # that only runs when requested. + quarantine! do + it_behaves_like :syslog_log, :emerg + end + end +end diff --git a/spec/ruby/library/syslog/err_spec.rb b/spec/ruby/library/syslog/err_spec.rb new file mode 100644 index 0000000000..43e876ed37 --- /dev/null +++ b/spec/ruby/library/syslog/err_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.err" do + it_behaves_like :syslog_log, :err + end +end diff --git a/spec/ruby/library/syslog/facility_spec.rb b/spec/ruby/library/syslog/facility_spec.rb new file mode 100644 index 0000000000..550ca70b11 --- /dev/null +++ b/spec/ruby/library/syslog/facility_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.facility" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns the logging facility" do + Syslog.open("rubyspec", 3, Syslog::LOG_MAIL) + Syslog.facility.should == Syslog::LOG_MAIL + Syslog.close + end + + it "returns nil if the log is closed" do + Syslog.opened?.should be_false + Syslog.facility.should == nil + end + + it "defaults to LOG_USER" do + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + Syslog.close + end + + it "resets after each open call" do + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + + Syslog.open!("rubyspec", 3, Syslog::LOG_MAIL) + Syslog.facility.should == Syslog::LOG_MAIL + Syslog.close + + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + Syslog.close + end + end + end +end diff --git a/spec/ruby/library/syslog/ident_spec.rb b/spec/ruby/library/syslog/ident_spec.rb new file mode 100644 index 0000000000..3b08327140 --- /dev/null +++ b/spec/ruby/library/syslog/ident_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.ident" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns the logging identity" do + Syslog.open("rubyspec") + Syslog.ident.should == "rubyspec" + Syslog.close + end + + it "returns nil if the log is closed" do + Syslog.should_not.opened? + Syslog.ident.should == nil + end + + it "defaults to $0" do + Syslog.open + Syslog.ident.should == $0 + Syslog.close + end + end + end +end diff --git a/spec/ruby/library/syslog/info_spec.rb b/spec/ruby/library/syslog/info_spec.rb new file mode 100644 index 0000000000..f2d535299c --- /dev/null +++ b/spec/ruby/library/syslog/info_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.info" do + it_behaves_like :syslog_log, :info + end +end diff --git a/spec/ruby/library/syslog/inspect_spec.rb b/spec/ruby/library/syslog/inspect_spec.rb new file mode 100644 index 0000000000..f45231f8e3 --- /dev/null +++ b/spec/ruby/library/syslog/inspect_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.inspect" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns a string a closed log" do + Syslog.inspect.should =~ /opened=false/ + end + + it "returns a string for an opened log" do + Syslog.open + Syslog.inspect.should =~ /opened=true.*/ + Syslog.close + end + + it "includes the ident, options, facility and mask" do + Syslog.open("rubyspec", Syslog::LOG_PID, Syslog::LOG_USER) + inspect_str = Syslog.inspect.split ", " + inspect_str[0].should =~ /opened=true/ + inspect_str[1].should == "ident=\"rubyspec\"" + inspect_str[2].should == "options=#{Syslog::LOG_PID}" + inspect_str[3].should == "facility=#{Syslog::LOG_USER}" + inspect_str[4].should == "mask=255>" + Syslog.close + end + end + end +end diff --git a/spec/ruby/library/syslog/instance_spec.rb b/spec/ruby/library/syslog/instance_spec.rb new file mode 100644 index 0000000000..891296c52d --- /dev/null +++ b/spec/ruby/library/syslog/instance_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.instance" do + platform_is_not :windows do + it "returns the module" do + Syslog.instance.should == Syslog + end + end + end +end diff --git a/spec/ruby/library/syslog/log_spec.rb b/spec/ruby/library/syslog/log_spec.rb new file mode 100644 index 0000000000..0c855b8257 --- /dev/null +++ b/spec/ruby/library/syslog/log_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.log" do + platform_is_not :windows, :darwin, :aix, :android do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "receives a priority as first argument" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| + s.log(Syslog::LOG_ALERT, "Hello") + s.log(Syslog::LOG_CRIT, "World") + end + }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\nrubyspec(?::| \d+ - -) World\n\z/, $stderr) + end + + it "accepts undefined priorities" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| + s.log(1337, "Hello") + end + # use a regex since it'll output unknown facility/priority messages + }.should output_to_fd(/rubyspec(?::| \d+ - -) Hello\n\z/, $stderr) + end + + it "fails with TypeError on nil log messages" do + Syslog.open do |s| + -> { s.log(1, nil) }.should raise_error(TypeError) + end + end + + it "fails if the log is closed" do + -> { + Syslog.log(Syslog::LOG_ALERT, "test") + }.should raise_error(RuntimeError) + end + + it "accepts printf parameters" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| + s.log(Syslog::LOG_ALERT, "%s x %d", "chunky bacon", 2) + end + }.should output_to_fd(/rubyspec(?::| \d+ - -) chunky bacon x 2\n\z/, $stderr) + end + end + end +end diff --git a/spec/ruby/library/syslog/mask_spec.rb b/spec/ruby/library/syslog/mask_spec.rb new file mode 100644 index 0000000000..b3f1250b24 --- /dev/null +++ b/spec/ruby/library/syslog/mask_spec.rb @@ -0,0 +1,113 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.mask" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + # make sure we return the mask to the default value + Syslog.open { |s| s.mask = 255 } + end + + it "returns the log priority mask" do + Syslog.open("rubyspec") do + Syslog.mask.should == 255 + Syslog.mask = 3 + Syslog.mask.should == 3 + Syslog.mask = 255 + end + end + + it "defaults to 255" do + Syslog.open do |s| + s.mask.should == 255 + end + end + + it "returns nil if the log is closed" do + Syslog.should_not.opened? + Syslog.mask.should == nil + end + + platform_is :darwin do + it "resets if the log is reopened" do + Syslog.open + Syslog.mask.should == 255 + Syslog.mask = 64 + + Syslog.reopen("rubyspec") do + Syslog.mask.should == 255 + end + + Syslog.open do + Syslog.mask.should == 255 + end + end + end + + platform_is_not :darwin do + it "persists if the log is reopened" do + Syslog.open + Syslog.mask.should == 255 + Syslog.mask = 64 + + Syslog.reopen("rubyspec") do + Syslog.mask.should == 64 + end + + Syslog.open do + Syslog.mask.should == 64 + end + end + end + end + end + + describe "Syslog.mask=" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + # make sure we return the mask to the default value + Syslog.open { |s| s.mask = 255 } + end + + it "sets the log priority mask" do + Syslog.open do + Syslog.mask = 64 + Syslog.mask.should == 64 + end + end + + it "raises an error if the log is closed" do + -> { Syslog.mask = 1337 }.should raise_error(RuntimeError) + end + + it "only accepts numbers" do + Syslog.open do + + Syslog.mask = 1337 + Syslog.mask.should == 1337 + + Syslog.mask = 3.1416 + Syslog.mask.should == 3 + + -> { Syslog.mask = "oh hai" }.should raise_error(TypeError) + -> { Syslog.mask = "43" }.should raise_error(TypeError) + + end + end + end + end +end diff --git a/spec/ruby/library/syslog/notice_spec.rb b/spec/ruby/library/syslog/notice_spec.rb new file mode 100644 index 0000000000..a2134e0140 --- /dev/null +++ b/spec/ruby/library/syslog/notice_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.notice" do + it_behaves_like :syslog_log, :notice + end +end diff --git a/spec/ruby/library/syslog/open_spec.rb b/spec/ruby/library/syslog/open_spec.rb new file mode 100644 index 0000000000..543f5d418b --- /dev/null +++ b/spec/ruby/library/syslog/open_spec.rb @@ -0,0 +1,92 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/reopen' + require 'syslog' + + describe "Syslog.open" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns the module" do + Syslog.open.should == Syslog + Syslog.close + Syslog.open("Test", 5, 9).should == Syslog + Syslog.close + end + + it "receives an identity as first argument" do + Syslog.open("rubyspec") + Syslog.ident.should == "rubyspec" + Syslog.close + end + + it "defaults the identity to $0" do + Syslog.open + Syslog.ident.should == $0 + Syslog.close + end + + it "receives the logging options as second argument" do + Syslog.open("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + end + + it "defaults the logging options to LOG_PID | LOG_CONS" do + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + Syslog.close + end + + it "receives a facility as third argument" do + Syslog.open("rubyspec", Syslog::LOG_PID, 0) + Syslog.facility.should == 0 + Syslog.close + end + + it "defaults the facility to LOG_USER" do + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + Syslog.close + end + + it "receives a block and calls it with the module" do + Syslog.open("rubyspec", 3, 8) do |s| + s.should == Syslog + s.ident.should == "rubyspec" + s.options.should == 3 + s.facility.should == Syslog::LOG_USER + end + end + + it "closes the log if after it receives a block" do + Syslog.open{ } + Syslog.opened?.should be_false + end + + it "raises an error if the log is opened" do + Syslog.open + -> { + Syslog.open + }.should raise_error(RuntimeError, /syslog already open/) + -> { + Syslog.close + Syslog.open + }.should_not raise_error + Syslog.close + end + end + end + + describe "Syslog.open!" do + it_behaves_like :syslog_reopen, :open! + end +end diff --git a/spec/ruby/library/syslog/opened_spec.rb b/spec/ruby/library/syslog/opened_spec.rb new file mode 100644 index 0000000000..94432e65a4 --- /dev/null +++ b/spec/ruby/library/syslog/opened_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.opened?" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns true if the log is opened" do + Syslog.open + Syslog.opened?.should be_true + Syslog.close + end + + it "returns false otherwise" do + Syslog.opened?.should be_false + Syslog.open + Syslog.close + Syslog.opened?.should be_false + end + + it "works inside a block" do + Syslog.open do |s| + s.opened?.should be_true + Syslog.opened?.should be_true + end + Syslog.opened?.should be_false + end + end + end +end diff --git a/spec/ruby/library/syslog/options_spec.rb b/spec/ruby/library/syslog/options_spec.rb new file mode 100644 index 0000000000..83ba43503e --- /dev/null +++ b/spec/ruby/library/syslog/options_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require 'syslog' + + describe "Syslog.options" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns the logging options" do + Syslog.open("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + end + + it "returns nil when the log is closed" do + Syslog.opened?.should be_false + Syslog.options.should == nil + end + + it "defaults to LOG_PID | LOG_CONS" do + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + Syslog.close + end + + it "resets after each open call" do + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + + Syslog.open!("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + Syslog.close + end + end + end +end diff --git a/spec/ruby/library/syslog/reopen_spec.rb b/spec/ruby/library/syslog/reopen_spec.rb new file mode 100644 index 0000000000..a78529fa1f --- /dev/null +++ b/spec/ruby/library/syslog/reopen_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/reopen' + require 'syslog' + + describe "Syslog.reopen" do + it_behaves_like :syslog_reopen, :reopen + end +end diff --git a/spec/ruby/library/syslog/shared/log.rb b/spec/ruby/library/syslog/shared/log.rb new file mode 100644 index 0000000000..9f9302b214 --- /dev/null +++ b/spec/ruby/library/syslog/shared/log.rb @@ -0,0 +1,39 @@ +describe :syslog_log, shared: true do + platform_is_not :windows, :darwin, :aix, :android do + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "logs a message" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do + Syslog.send(@method, "Hello") + end + }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\n\z/, $stderr) + end + + it "accepts sprintf arguments" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do + Syslog.send(@method, "Hello %s", "world") + Syslog.send(@method, "%d dogs", 2) + end + }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello world\nrubyspec(?::| \d+ - -) 2 dogs\n\z/, $stderr) + end + + it "works as an alias for Syslog.log" do + level = Syslog.const_get "LOG_#{@method.to_s.upcase}" + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do + Syslog.send(@method, "Hello") + Syslog.log(level, "Hello") + end + # make sure the same thing is written to $stderr. + }.should output_to_fd(/\A(?:rubyspec(?::| \d+ - -) Hello\n){2}\z/, $stderr) + end + end +end diff --git a/spec/ruby/library/syslog/shared/reopen.rb b/spec/ruby/library/syslog/shared/reopen.rb new file mode 100644 index 0000000000..621437a01d --- /dev/null +++ b/spec/ruby/library/syslog/shared/reopen.rb @@ -0,0 +1,40 @@ +describe :syslog_reopen, shared: true do + platform_is_not :windows do + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "reopens the log" do + Syslog.open + -> { Syslog.send(@method)}.should_not raise_error + Syslog.opened?.should be_true + Syslog.close + end + + it "fails with RuntimeError if the log is closed" do + -> { Syslog.send(@method)}.should raise_error(RuntimeError) + end + + it "receives the same parameters as Syslog.open" do + Syslog.open + Syslog.send(@method, "rubyspec", 3, 8) do |s| + s.should == Syslog + s.ident.should == "rubyspec" + s.options.should == 3 + s.facility.should == Syslog::LOG_USER + s.opened?.should be_true + end + Syslog.opened?.should be_false + end + + it "returns the module" do + Syslog.open + Syslog.send(@method).should == Syslog + Syslog.close + end + end +end diff --git a/spec/ruby/library/syslog/warning_spec.rb b/spec/ruby/library/syslog/warning_spec.rb new file mode 100644 index 0000000000..eeca603136 --- /dev/null +++ b/spec/ruby/library/syslog/warning_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' + + describe "Syslog.warning" do + it_behaves_like :syslog_log, :warning + end +end diff --git a/spec/ruby/library/tempfile/_close_spec.rb b/spec/ruby/library/tempfile/_close_spec.rb new file mode 100644 index 0000000000..c08f425b6f --- /dev/null +++ b/spec/ruby/library/tempfile/_close_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile#_close" do + before :each do + @tempfile = Tempfile.new("specs") + end + + after :each do + @tempfile.close! + end + + it "is protected" do + Tempfile.should have_protected_instance_method(:_close) + end + + it "closes self" do + @tempfile.send(:_close) + @tempfile.closed?.should be_true + end +end diff --git a/spec/ruby/library/tempfile/close_spec.rb b/spec/ruby/library/tempfile/close_spec.rb new file mode 100644 index 0000000000..db0eae3fa5 --- /dev/null +++ b/spec/ruby/library/tempfile/close_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile#close when passed no argument or [false]" do + before :each do + @tempfile = Tempfile.new("specs", tmp("")) + end + + after :each do + @tempfile.close! + end + + it "closes self" do + @tempfile.close + @tempfile.closed?.should be_true + end + + it "does not unlink self" do + path = @tempfile.path + @tempfile.close + File.should.exist?(path) + end +end + +describe "Tempfile#close when passed [true]" do + before :each do + @tempfile = Tempfile.new("specs", tmp("")) + end + + it "closes self" do + @tempfile.close(true) + @tempfile.closed?.should be_true + end + + it "unlinks self" do + path = @tempfile.path + @tempfile.close(true) + File.should_not.exist?(path) + end +end + +describe "Tempfile#close!" do + before :each do + @tempfile = Tempfile.new("specs", tmp("")) + end + + it "closes self" do + @tempfile.close! + @tempfile.closed?.should be_true + end + + it "unlinks self" do + path = @tempfile.path + @tempfile.close! + File.should_not.exist?(path) + end +end diff --git a/spec/ruby/library/tempfile/create_spec.rb b/spec/ruby/library/tempfile/create_spec.rb new file mode 100644 index 0000000000..74c48bf32a --- /dev/null +++ b/spec/ruby/library/tempfile/create_spec.rb @@ -0,0 +1,176 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile.create" do + after :each do + if @tempfile + @tempfile.close + File.unlink(@tempfile.path) if File.file?(@tempfile.path) + end + end + + it "returns a new, open regular File instance placed in tmpdir" do + @tempfile = Tempfile.create + # Unlike Tempfile.open this returns a true File, + # but `.should be_an_instance_of(File)` would be true either way. + @tempfile.instance_of?(File).should be_true + + @tempfile.should_not.closed? + File.file?(@tempfile.path).should be_true + + @tempfile.path.should.start_with?(Dir.tmpdir) + @tempfile.path.should_not == "#{Dir.tmpdir}/" + end + + it "returns file in w+ mode" do + @tempfile = Tempfile.create + @tempfile << "Test!\nMore test!" + @tempfile.rewind + @tempfile.read.should == "Test!\nMore test!" + + # Not "a+" mode, which would write at the end of the file. + @tempfile.rewind + @tempfile.print "Trust" + @tempfile.rewind + @tempfile.read.should == "Trust\nMore test!" + end + + platform_is_not :windows do + it "returns a private, readable and writable file" do + @tempfile = Tempfile.create + stat = @tempfile.stat + stat.should.readable? + stat.should.writable? + stat.should_not.executable? + stat.should_not.world_readable? + stat.should_not.world_writable? + end + end + + platform_is :windows do + it "returns a public, readable and writable file" do + @tempfile = Tempfile.create + stat = @tempfile.stat + stat.should.readable? + stat.should.writable? + stat.should_not.executable? + stat.should.world_readable? + stat.should.world_writable? + end + end + + context "when called with a block" do + it "returns the value of the block" do + value = Tempfile.create do |tempfile| + tempfile << "Test!" + "return" + end + value.should == "return" + end + + it "closes and unlinks file after block execution" do + Tempfile.create do |tempfile| + @tempfile = tempfile + @tempfile.should_not.closed? + File.exist?(@tempfile.path).should be_true + end + + @tempfile.should.closed? + File.exist?(@tempfile.path).should be_false + end + end + + context "when called with a single positional argument" do + it "uses a String as a prefix for the filename" do + @tempfile = Tempfile.create("create_spec") + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should_not == "#{Dir.tmpdir}/create_spec" + end + + it "uses an array of one String as a prefix for the filename" do + @tempfile = Tempfile.create(["create_spec"]) + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should_not == "#{Dir.tmpdir}/create_spec" + end + + it "uses an array of two Strings as a prefix and suffix for the filename" do + @tempfile = Tempfile.create(["create_spec", ".temp"]) + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should.end_with?(".temp") + end + + it "ignores excessive array elements after the first two" do + @tempfile = Tempfile.create(["create_spec", ".temp", :".txt"]) + @tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec") + @tempfile.path.should.end_with?(".temp") + end + + it "raises ArgumentError if passed something else than a String or an array of Strings" do + -> { Tempfile.create(:create_spec) }.should raise_error(ArgumentError, "unexpected prefix: :create_spec") + -> { Tempfile.create([:create_spec]) }.should raise_error(ArgumentError, "unexpected prefix: :create_spec") + -> { Tempfile.create(["create_spec", :temp]) }.should raise_error(ArgumentError, "unexpected suffix: :temp") + end + end + + context "when called with a second positional argument" do + it "uses it as a directory for the tempfile" do + @tempfile = Tempfile.create("create_spec", "./") + @tempfile.path.should.start_with?("./create_spec") + end + + it "raises TypeError if argument can not be converted to a String" do + -> { Tempfile.create("create_spec", :temp) }.should raise_error(TypeError, "no implicit conversion of Symbol into String") + end + end + + context "when called with a mode option" do + it "ORs it with the default mode, forcing it to be readable and writable" do + @tempfile = Tempfile.create(mode: File::RDONLY) + @tempfile.puts "test" + @tempfile.rewind + @tempfile.read.should == "test\n" + end + + it "raises NoMethodError if passed a String mode" do + -> { Tempfile.create(mode: "wb") }.should raise_error(NoMethodError, /undefined method ['`]|' for .+String/) + end + end + + ruby_version_is "3.4" do + context "when called with anonymous: true" do + it "returns an already unlinked File without a proper path" do + @tempfile = Tempfile.create(anonymous: true) + @tempfile.should_not.closed? + @tempfile.path.should == "#{Dir.tmpdir}/" + File.file?(@tempfile.path).should be_false + end + + it "unlinks file before calling the block" do + Tempfile.create(anonymous: true) do |tempfile| + @tempfile = tempfile + @tempfile.should_not.closed? + @tempfile.path.should == "#{Dir.tmpdir}/" + File.file?(@tempfile.path).should be_false + end + @tempfile.should.closed? + end + end + + context "when called with anonymous: false" do + it "returns a usual File with a path" do + @tempfile = Tempfile.create(anonymous: false) + @tempfile.should_not.closed? + @tempfile.path.should.start_with?(Dir.tmpdir) + File.file?(@tempfile.path).should be_true + end + end + end + + context "when called with other options" do + it "passes them along to File.open" do + @tempfile = Tempfile.create(encoding: "IBM037:IBM037", binmode: true) + @tempfile.external_encoding.should == Encoding.find("IBM037") + @tempfile.binmode?.should be_true + end + end +end diff --git a/spec/ruby/library/tempfile/delete_spec.rb b/spec/ruby/library/tempfile/delete_spec.rb new file mode 100644 index 0000000000..0332b44dde --- /dev/null +++ b/spec/ruby/library/tempfile/delete_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/unlink' +require 'tempfile' + +describe "Tempfile#delete" do + it_behaves_like :tempfile_unlink, :delete +end diff --git a/spec/ruby/library/tempfile/initialize_spec.rb b/spec/ruby/library/tempfile/initialize_spec.rb new file mode 100644 index 0000000000..f2e786d7d8 --- /dev/null +++ b/spec/ruby/library/tempfile/initialize_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile#initialize" do + before :each do + @tempfile = Tempfile.allocate + end + + after :each do + @tempfile.close! + end + + it "opens a new tempfile with the passed name in the passed directory" do + @tempfile.send(:initialize, "basename", tmp("")) + File.should.exist?(@tempfile.path) + + tmpdir = tmp("") + path = @tempfile.path + + platform_is :windows do + # on Windows, both types of slashes are OK, + # but the tmp helper always uses '/' + path.gsub!('\\', '/') + end + + path[0, tmpdir.length].should == tmpdir + path.should include("basename") + end + + platform_is_not :windows do + it "sets the permissions on the tempfile to 0600" do + @tempfile.send(:initialize, "basename", tmp("")) + File.stat(@tempfile.path).mode.should == 0100600 + end + end + + it "accepts encoding options" do + @tempfile.send(:initialize, ['shiftjis', 'yml'], encoding: 'SHIFT_JIS') + @tempfile.external_encoding.should == Encoding::Shift_JIS + end + + it "does not try to modify the arguments" do + @tempfile.send(:initialize, ['frozen'.freeze, 'txt'.freeze], encoding: Encoding::IBM437) + @tempfile.external_encoding.should == Encoding::IBM437 + end +end diff --git a/spec/ruby/library/tempfile/length_spec.rb b/spec/ruby/library/tempfile/length_spec.rb new file mode 100644 index 0000000000..bc622b9a70 --- /dev/null +++ b/spec/ruby/library/tempfile/length_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/length' +require 'tempfile' + +describe "Tempfile#length" do + it_behaves_like :tempfile_length, :length +end diff --git a/spec/ruby/library/tempfile/open_spec.rb b/spec/ruby/library/tempfile/open_spec.rb new file mode 100644 index 0000000000..ef2c95376f --- /dev/null +++ b/spec/ruby/library/tempfile/open_spec.rb @@ -0,0 +1,97 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile#open" do + before :each do + @tempfile = Tempfile.new("specs") + @tempfile.puts("Test!") + end + + after :each do + @tempfile.close! + end + + it "reopens self" do + @tempfile.close + @tempfile.open + @tempfile.closed?.should be_false + end + + it "reopens self in read and write mode and does not truncate" do + @tempfile.open + @tempfile.puts("Another Test!") + + @tempfile.open + @tempfile.readline.should == "Another Test!\n" + end +end + +describe "Tempfile.open" do + after :each do + @tempfile.close! if @tempfile + end + + it "returns a new, open Tempfile instance" do + @tempfile = Tempfile.open("specs") + # Delegation messes up .should be_an_instance_of(Tempfile) + @tempfile.instance_of?(Tempfile).should be_true + end + + it "is passed an array [base, suffix] as first argument" do + Tempfile.open(["specs", ".tt"]) { |tempfile| @tempfile = tempfile } + @tempfile.path.should =~ /specs.*\.tt$/ + end + + it "passes the third argument (options) to open" do + Tempfile.open("specs", Dir.tmpdir, encoding: "IBM037:IBM037", binmode: true) do |tempfile| + @tempfile = tempfile + tempfile.external_encoding.should == Encoding.find("IBM037") + tempfile.binmode?.should be_true + end + end + + it "uses a blank string for basename when passed no arguments" do + Tempfile.open() do |tempfile| + @tempfile = tempfile + tempfile.closed?.should be_false + end + @tempfile.should_not == nil + end +end + +describe "Tempfile.open when passed a block" do + before :each do + ScratchPad.clear + end + + after :each do + # Tempfile.open with block does not unlink + @tempfile.close! if @tempfile + end + + it "yields a new, open Tempfile instance to the block" do + Tempfile.open("specs") do |tempfile| + @tempfile = tempfile + ScratchPad.record :yielded + + # Delegation messes up .should be_an_instance_of(Tempfile) + tempfile.instance_of?(Tempfile).should be_true + tempfile.closed?.should be_false + end + + ScratchPad.recorded.should == :yielded + end + + it "returns the value of the block" do + value = Tempfile.open("specs") do |tempfile| + @tempfile = tempfile + "return" + end + value.should == "return" + end + + it "closes the yielded Tempfile after the block" do + Tempfile.open("specs") { |tempfile| @tempfile = tempfile } + @tempfile.closed?.should be_true + end +end diff --git a/spec/ruby/library/tempfile/path_spec.rb b/spec/ruby/library/tempfile/path_spec.rb new file mode 100644 index 0000000000..07f75b3e10 --- /dev/null +++ b/spec/ruby/library/tempfile/path_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'tempfile' + +describe "Tempfile#path" do + before :each do + @tempfile = Tempfile.new("specs", tmp("")) + end + + after :each do + @tempfile.close! + end + + it "returns the path to the tempfile" do + tmpdir = tmp("") + path = @tempfile.path + + platform_is :windows do + # on Windows, both types of slashes are OK, + # but the tmp helper always uses '/' + path.gsub!('\\', '/') + end + + path[0, tmpdir.length].should == tmpdir + path.should include("specs") + end +end diff --git a/spec/ruby/library/tempfile/shared/length.rb b/spec/ruby/library/tempfile/shared/length.rb new file mode 100644 index 0000000000..4d18d1f385 --- /dev/null +++ b/spec/ruby/library/tempfile/shared/length.rb @@ -0,0 +1,21 @@ +describe :tempfile_length, shared: true do + before :each do + @tempfile = Tempfile.new("specs") + end + + after :each do + @tempfile.close! + end + + it "returns the size of self" do + @tempfile.send(@method).should eql(0) + @tempfile.print("Test!") + @tempfile.send(@method).should eql(5) + end + + it "returns the size of self even if self is closed" do + @tempfile.print("Test!") + @tempfile.close + @tempfile.send(@method).should eql(5) + end +end diff --git a/spec/ruby/library/tempfile/shared/unlink.rb b/spec/ruby/library/tempfile/shared/unlink.rb new file mode 100644 index 0000000000..e821228d70 --- /dev/null +++ b/spec/ruby/library/tempfile/shared/unlink.rb @@ -0,0 +1,12 @@ +describe :tempfile_unlink, shared: true do + before :each do + @tempfile = Tempfile.new("specs") + end + + it "unlinks self" do + @tempfile.close + path = @tempfile.path + @tempfile.send(@method) + File.should_not.exist?(path) + end +end diff --git a/spec/ruby/library/tempfile/size_spec.rb b/spec/ruby/library/tempfile/size_spec.rb new file mode 100644 index 0000000000..f4824601c7 --- /dev/null +++ b/spec/ruby/library/tempfile/size_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/length' +require 'tempfile' + +describe "Tempfile#size" do + it_behaves_like :tempfile_length, :size +end diff --git a/spec/ruby/library/tempfile/unlink_spec.rb b/spec/ruby/library/tempfile/unlink_spec.rb new file mode 100644 index 0000000000..eac7df8472 --- /dev/null +++ b/spec/ruby/library/tempfile/unlink_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/unlink' +require 'tempfile' + +describe "Tempfile#unlink" do + it_behaves_like :tempfile_unlink, :unlink +end diff --git a/spec/ruby/library/thread/queue_spec.rb b/spec/ruby/library/thread/queue_spec.rb new file mode 100644 index 0000000000..c7e2bb1b50 --- /dev/null +++ b/spec/ruby/library/thread/queue_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' + +describe "Thread::Queue" do + it "is the same class as ::Queue" do + Thread.should have_constant(:Queue) + Thread::Queue.should equal ::Queue + end +end diff --git a/spec/ruby/library/thread/sizedqueue_spec.rb b/spec/ruby/library/thread/sizedqueue_spec.rb new file mode 100644 index 0000000000..6151ff437c --- /dev/null +++ b/spec/ruby/library/thread/sizedqueue_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' + +describe "Thread::SizedQueue" do + it "is the same class as ::SizedQueue" do + Thread.should have_constant(:SizedQueue) + Thread::SizedQueue.should equal ::SizedQueue + end +end diff --git a/spec/ruby/library/time/httpdate_spec.rb b/spec/ruby/library/time/httpdate_spec.rb new file mode 100644 index 0000000000..90953a9307 --- /dev/null +++ b/spec/ruby/library/time/httpdate_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require 'time' + +describe "Time.httpdate" do + it "parses RFC-2616 strings" do + t = Time.utc(1994, 11, 6, 8, 49, 37) + t.should == Time.httpdate("Sun, 06 Nov 1994 08:49:37 GMT") + + # relies on Time.parse (not yet implemented) + # t.should == Time.httpdate("Sunday, 06-Nov-94 08:49:37 GMT") + + t.should == Time.httpdate("Sun Nov 6 08:49:37 1994") + Time.utc(1995, 11, 15, 6, 25, 24).should == Time.httpdate("Wed, 15 Nov 1995 06:25:24 GMT") + Time.utc(1995, 11, 15, 4, 58, 8).should == Time.httpdate("Wed, 15 Nov 1995 04:58:08 GMT") + Time.utc(1994, 11, 15, 8, 12, 31).should == Time.httpdate("Tue, 15 Nov 1994 08:12:31 GMT") + Time.utc(1994, 12, 1, 16, 0, 0).should == Time.httpdate("Thu, 01 Dec 1994 16:00:00 GMT") + Time.utc(1994, 10, 29, 19, 43, 31).should == Time.httpdate("Sat, 29 Oct 1994 19:43:31 GMT") + Time.utc(1994, 11, 15, 12, 45, 26).should == Time.httpdate("Tue, 15 Nov 1994 12:45:26 GMT") + Time.utc(1999, 12, 31, 23, 59, 59).should == Time.httpdate("Fri, 31 Dec 1999 23:59:59 GMT") + end +end diff --git a/spec/ruby/library/time/iso8601_spec.rb b/spec/ruby/library/time/iso8601_spec.rb new file mode 100644 index 0000000000..ab35ab25d6 --- /dev/null +++ b/spec/ruby/library/time/iso8601_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' +require 'time' + +describe "Time.iso8601" do + it_behaves_like :time_library_xmlschema, :iso8601 +end diff --git a/spec/ruby/library/time/rfc2822_spec.rb b/spec/ruby/library/time/rfc2822_spec.rb new file mode 100644 index 0000000000..7fc5e9a64b --- /dev/null +++ b/spec/ruby/library/time/rfc2822_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/rfc2822' +require 'time' + +describe "Time.rfc2822" do + it_behaves_like :time_rfc2822, :rfc2822 +end diff --git a/spec/ruby/library/time/rfc822_spec.rb b/spec/ruby/library/time/rfc822_spec.rb new file mode 100644 index 0000000000..da77e6ee77 --- /dev/null +++ b/spec/ruby/library/time/rfc822_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/rfc2822' +require 'time' + +describe "Time.rfc822" do + it_behaves_like :time_rfc2822, :rfc822 +end diff --git a/spec/ruby/library/time/shared/rfc2822.rb b/spec/ruby/library/time/shared/rfc2822.rb new file mode 100644 index 0000000000..e460d655a6 --- /dev/null +++ b/spec/ruby/library/time/shared/rfc2822.rb @@ -0,0 +1,65 @@ +describe :time_rfc2822, shared: true do + it "parses RFC-822 strings" do + t1 = (Time.utc(1976, 8, 26, 14, 30) + 4 * 3600) + t2 = Time.send(@method, "26 Aug 76 14:30 EDT") + t1.should == t2 + + t3 = Time.utc(1976, 8, 27, 9, 32) + 7 * 3600 + t4 = Time.send(@method, "27 Aug 76 09:32 PDT") + t3.should == t4 + end + + it "parses RFC-2822 strings" do + t1 = Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600 + t2 = Time.send(@method, "Fri, 21 Nov 1997 09:55:06 -0600") + t1.should == t2 + + t3 = Time.utc(2003, 7, 1, 10, 52, 37) - 2 * 3600 + t4 = Time.send(@method, "Tue, 1 Jul 2003 10:52:37 +0200") + t3.should == t4 + + t5 = Time.utc(1997, 11, 21, 10, 1, 10) + 6 * 3600 + t6 = Time.send(@method, "Fri, 21 Nov 1997 10:01:10 -0600") + t5.should == t6 + + t7 = Time.utc(1997, 11, 21, 11, 0, 0) + 6 * 3600 + t8 = Time.send(@method, "Fri, 21 Nov 1997 11:00:00 -0600") + t7.should == t8 + + t9 = Time.utc(1997, 11, 24, 14, 22, 1) + 8 * 3600 + t10 = Time.send(@method, "Mon, 24 Nov 1997 14:22:01 -0800") + t9.should == t10 + + begin + Time.at(-1) + rescue ArgumentError + # ignore + else + t11 = Time.utc(1969, 2, 13, 23, 32, 54) + 3 * 3600 + 30 * 60 + t12 = Time.send(@method, "Thu, 13 Feb 1969 23:32:54 -0330") + t11.should == t12 + + t13 = Time.utc(1969, 2, 13, 23, 32, 0) + 3 * 3600 + 30 * 60 + t14 = Time.send(@method, " Thu, + 13 + Feb + 1969 + 23:32 + -0330 (Newfoundland Time)") + t13.should == t14 + end + + t15 = Time.utc(1997, 11, 21, 9, 55, 6) + t16 = Time.send(@method, "21 Nov 97 09:55:06 GMT") + t15.should == t16 + + t17 = Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600 + t18 = Time.send(@method, "Fri, 21 Nov 1997 09 : 55 : 06 -0600") + t17.should == t18 + + -> { + # inner comment is not supported. + Time.send(@method, "Fri, 21 Nov 1997 09(comment): 55 : 06 -0600") + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/time/shared/xmlschema.rb b/spec/ruby/library/time/shared/xmlschema.rb new file mode 100644 index 0000000000..0002886ca5 --- /dev/null +++ b/spec/ruby/library/time/shared/xmlschema.rb @@ -0,0 +1,53 @@ +describe :time_library_xmlschema, shared: true do + it "parses ISO-8601 strings" do + t = Time.utc(1985, 4, 12, 23, 20, 50, 520000) + s = "1985-04-12T23:20:50.52Z" + t.should == Time.send(@method, s) + #s.should == t.send(@method, 2) + + t = Time.utc(1996, 12, 20, 0, 39, 57) + s = "1996-12-19T16:39:57-08:00" + t.should == Time.send(@method, s) + # There is no way to generate time string with arbitrary timezone. + s = "1996-12-20T00:39:57Z" + t.should == Time.send(@method, s) + #assert_equal(s, t.send(@method)) + + t = Time.utc(1990, 12, 31, 23, 59, 60) + s = "1990-12-31T23:59:60Z" + t.should == Time.send(@method, s) + # leap second is representable only if timezone file has it. + s = "1990-12-31T15:59:60-08:00" + t.should == Time.send(@method, s) + + begin + Time.at(-1) + rescue ArgumentError + # ignore + else + t = Time.utc(1937, 1, 1, 11, 40, 27, 870000) + s = "1937-01-01T12:00:27.87+00:20" + t.should == Time.send(@method, s) + end + + # more + + # (Time.utc(1999, 5, 31, 13, 20, 0) + 5 * 3600).should == Time.send(@method, "1999-05-31T13:20:00-05:00") + # (Time.local(2000, 1, 20, 12, 0, 0)).should == Time.send(@method, "2000-01-20T12:00:00") + # (Time.utc(2000, 1, 20, 12, 0, 0)).should == Time.send(@method, "2000-01-20T12:00:00Z") + # (Time.utc(2000, 1, 20, 12, 0, 0) - 12 * 3600).should == Time.send(@method, "2000-01-20T12:00:00+12:00") + # (Time.utc(2000, 1, 20, 12, 0, 0) + 13 * 3600).should == Time.send(@method, "2000-01-20T12:00:00-13:00") + # (Time.utc(2000, 3, 4, 23, 0, 0) - 3 * 3600).should == Time.send(@method, "2000-03-04T23:00:00+03:00") + # (Time.utc(2000, 3, 4, 20, 0, 0)).should == Time.send(@method, "2000-03-04T20:00:00Z") + # (Time.local(2000, 1, 15, 0, 0, 0)).should == Time.send(@method, "2000-01-15T00:00:00") + # (Time.local(2000, 2, 15, 0, 0, 0)).should == Time.send(@method, "2000-02-15T00:00:00") + # (Time.local(2000, 1, 15, 12, 0, 0)).should == Time.send(@method, "2000-01-15T12:00:00") + # (Time.utc(2000, 1, 16, 12, 0, 0)).should == Time.send(@method, "2000-01-16T12:00:00Z") + # (Time.local(2000, 1, 1, 12, 0, 0)).should == Time.send(@method, "2000-01-01T12:00:00") + # (Time.utc(1999, 12, 31, 23, 0, 0)).should == Time.send(@method, "1999-12-31T23:00:00Z") + # (Time.local(2000, 1, 16, 12, 0, 0)).should == Time.send(@method, "2000-01-16T12:00:00") + # (Time.local(2000, 1, 16, 0, 0, 0)).should == Time.send(@method, "2000-01-16T00:00:00") + # (Time.utc(2000, 1, 12, 12, 13, 14)).should == Time.send(@method, "2000-01-12T12:13:14Z") + # (Time.utc(2001, 4, 17, 19, 23, 17, 300000)).should == Time.send(@method, "2001-04-17T19:23:17.3Z") + end +end diff --git a/spec/ruby/library/time/to_time_spec.rb b/spec/ruby/library/time/to_time_spec.rb new file mode 100644 index 0000000000..7e6c75a003 --- /dev/null +++ b/spec/ruby/library/time/to_time_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'time' + +describe "Time#to_time" do + it "returns itself in the same timezone" do + time = Time.new(2012, 2, 21, 10, 11, 12) + + with_timezone("America/Regina") do + time.to_time.should equal time + end + + time2 = Time.utc(2012, 2, 21, 10, 11, 12) + time2.to_time.should equal time2 + end +end diff --git a/spec/ruby/library/time/xmlschema_spec.rb b/spec/ruby/library/time/xmlschema_spec.rb new file mode 100644 index 0000000000..ff3c864a02 --- /dev/null +++ b/spec/ruby/library/time/xmlschema_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' +require 'time' + +describe "Time.xmlschema" do + it_behaves_like :time_library_xmlschema, :xmlschema +end diff --git a/spec/ruby/library/timeout/error_spec.rb b/spec/ruby/library/timeout/error_spec.rb new file mode 100644 index 0000000000..6c236e5128 --- /dev/null +++ b/spec/ruby/library/timeout/error_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'timeout' + +describe "Timeout::Error" do + it "is a subclass of RuntimeError" do + RuntimeError.should be_ancestor_of(Timeout::Error) + end +end diff --git a/spec/ruby/library/timeout/timeout_spec.rb b/spec/ruby/library/timeout/timeout_spec.rb new file mode 100644 index 0000000000..e16bcaea6a --- /dev/null +++ b/spec/ruby/library/timeout/timeout_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require 'timeout' + +describe "Timeout.timeout" do + it "raises Timeout::Error when it times out with no specified error type" do + -> { + Timeout.timeout(1) do + sleep + end + }.should raise_error(Timeout::Error) + end + + it "raises specified error type when it times out" do + -> do + Timeout.timeout(1, StandardError) do + sleep + end + end.should raise_error(StandardError) + end + + it "raises specified error type with specified message when it times out" do + -> do + Timeout.timeout(1, StandardError, "foobar") do + sleep + end + end.should raise_error(StandardError, "foobar") + end + + it "raises specified error type with a default message when it times out if message is nil" do + -> do + Timeout.timeout(1, StandardError, nil) do + sleep + end + end.should raise_error(StandardError, "execution expired") + end + + it "returns back the last value in the block" do + Timeout.timeout(1) do + 42 + end.should == 42 + end + + ruby_version_is "3.4" do + it "raises an ArgumentError when provided with a negative duration" do + -> { + Timeout.timeout(-1) + }.should raise_error(ArgumentError, "Timeout sec must be a non-negative number") + end + end +end diff --git a/spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb b/spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb new file mode 100644 index 0000000000..8165c2d8a8 --- /dev/null +++ b/spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb @@ -0,0 +1,117 @@ +require_relative '../../../spec_helper' +require "tmpdir" + +describe "Dir.mktmpdir when passed no arguments" do + after :each do + Dir.rmdir @tmpdir if File.directory? @tmpdir + end + + it "returns the path to the created tmp-dir" do + Dir.stub!(:mkdir) + Dir.should_receive(:tmpdir).and_return("/tmp") + @tmpdir = Dir.mktmpdir + @tmpdir.should =~ /^\/tmp\// + end + + it "creates a new writable directory in the path provided by Dir.tmpdir" do + Dir.should_receive(:tmpdir).and_return(tmp("")) + @tmpdir = Dir.mktmpdir + File.directory?(@tmpdir).should be_true + File.writable?(@tmpdir).should be_true + end +end + +describe "Dir.mktmpdir when passed a block" do + before :each do + @real_tmp_root = tmp('') + Dir.stub!(:tmpdir).and_return(@real_tmp_root) + FileUtils.stub!(:remove_entry) + FileUtils.stub!(:remove_entry_secure) + end + + after :each do + Dir.rmdir @tmpdir if File.directory? @tmpdir + end + + it "yields the path to the passed block" do + Dir.stub!(:mkdir) + called = nil + Dir.mktmpdir do |path| + @tmpdir = path + called = true + path.should.start_with?(@real_tmp_root) + end + called.should be_true + end + + it "creates the tmp-dir before yielding" do + Dir.should_receive(:tmpdir).and_return(tmp("")) + Dir.mktmpdir do |path| + @tmpdir = path + File.directory?(path).should be_true + File.writable?(path).should be_true + end + end + + it "removes the tmp-dir after executing the block" do + Dir.stub!(:mkdir) + Dir.mktmpdir do |path| + @tmpdir = path + FileUtils.should_receive(:remove_entry).with(path) + end + end + + it "returns the blocks return value" do + Dir.stub!(:mkdir) + result = Dir.mktmpdir do |path| + @tmpdir = path + :test + end + result.should equal(:test) + end +end + +describe "Dir.mktmpdir when passed [String]" do + before :each do + Dir.stub!(:mkdir) + Dir.stub!(:tmpdir).and_return("/tmp") + end + + after :each do + Dir.rmdir @tmpdir if File.directory? @tmpdir + end + + it "uses the passed String as a prefix to the tmp-directory" do + prefix = "before" + @tmpdir = Dir.mktmpdir(prefix) + @tmpdir.should =~ /^\/tmp\/#{prefix}/ + end +end + +describe "Dir.mktmpdir when passed [Array]" do + before :each do + Dir.stub!(:mkdir) + Dir.stub!(:tmpdir).and_return("/tmp") + FileUtils.stub!(:remove_entry_secure) + end + + after :each do + Dir.rmdir @tmpdir if File.directory? @tmpdir + end + + it "uses the first element of the passed Array as a prefix and the second element as a suffix to the tmp-directory" do + prefix = "before" + suffix = "after" + + @tmpdir = Dir.mktmpdir([prefix, suffix]) + @tmpdir.should =~ /#{suffix}$/ + end +end + +describe "Dir.mktmpdir when passed [Object]" do + it "raises an ArgumentError" do + -> { Dir.mktmpdir(Object.new) }.should raise_error(ArgumentError) + -> { Dir.mktmpdir(:symbol) }.should raise_error(ArgumentError) + -> { Dir.mktmpdir(10) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/tmpdir/dir/tmpdir_spec.rb b/spec/ruby/library/tmpdir/dir/tmpdir_spec.rb new file mode 100644 index 0000000000..f4ab5e40b8 --- /dev/null +++ b/spec/ruby/library/tmpdir/dir/tmpdir_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require "tmpdir" + +describe "Dir.tmpdir" do + it "returns the path to a writable and readable directory" do + dir = Dir.tmpdir + File.directory?(dir).should be_true + File.writable?(dir).should be_true + end +end diff --git a/spec/ruby/library/uri/decode_www_form_component_spec.rb b/spec/ruby/library/uri/decode_www_form_component_spec.rb new file mode 100644 index 0000000000..075cec1087 --- /dev/null +++ b/spec/ruby/library/uri/decode_www_form_component_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.decode_www_form_component" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/decode_www_form_spec.rb b/spec/ruby/library/uri/decode_www_form_spec.rb new file mode 100644 index 0000000000..8dd37e514f --- /dev/null +++ b/spec/ruby/library/uri/decode_www_form_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.decode_www_form" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/encode_www_form_component_spec.rb b/spec/ruby/library/uri/encode_www_form_component_spec.rb new file mode 100644 index 0000000000..a0508b207c --- /dev/null +++ b/spec/ruby/library/uri/encode_www_form_component_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.encode_www_form_component" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/encode_www_form_spec.rb b/spec/ruby/library/uri/encode_www_form_spec.rb new file mode 100644 index 0000000000..7f4aecf89b --- /dev/null +++ b/spec/ruby/library/uri/encode_www_form_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.encode_www_form" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/eql_spec.rb b/spec/ruby/library/uri/eql_spec.rb new file mode 100644 index 0000000000..2bbf8fd40c --- /dev/null +++ b/spec/ruby/library/uri/eql_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/normalization' +require_relative 'shared/eql' +require 'uri' + +describe "URI#eql?" do + it_behaves_like :uri_eql, :eql? + + it_behaves_like :uri_eql_against_other_types, :eql? +end diff --git a/spec/ruby/library/uri/equality_spec.rb b/spec/ruby/library/uri/equality_spec.rb new file mode 100644 index 0000000000..1c247ce291 --- /dev/null +++ b/spec/ruby/library/uri/equality_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/normalization' +require_relative 'shared/eql' +require 'uri' + +describe "URI#==" do + it "ignores capitalization of host names" do + URI("http://exAMPLE.cOm").should == URI("http://example.com") + end + + it "ignores capitalization of scheme" do + URI("hTTp://example.com").should == URI("http://example.com") + end + + it "treats a blank path and a path of '/' as the same" do + URI("http://example.com").should == URI("http://example.com/") + end + + it "is case sensitive in all components of the URI but the host and scheme" do + URI("http://example.com/paTH").should_not == URI("http://example.com/path") + URI("http://uSer@example.com").should_not == URI("http://user@example.com") + URI("http://example.com/path?quERy").should_not == URI("http://example.com/path?query") + URI("http://example.com/#fragMENT").should_not == URI("http://example.com/#fragment") + end + + it "differentiates based on port number" do + URI("http://example.com:8080").should_not == URI("http://example.com") + end + + # Note: The previous tests will be included in following ones + + it_behaves_like :uri_eql, :== + + it_behaves_like :uri_eql_against_other_types, :== + + quarantine! do # Quarantined until redmine:2542 is accepted + it "returns true only if the normalized forms are equivalent" do + URISpec::NORMALIZED_FORMS.each do |form| + normal_uri = URI(form[:normalized]) + form[:equivalent].each do |same| + URI(same).should == normal_uri + end + end + end + end +end diff --git a/spec/ruby/library/uri/escape/decode_spec.rb b/spec/ruby/library/uri/escape/decode_spec.rb new file mode 100644 index 0000000000..b4ef799411 --- /dev/null +++ b/spec/ruby/library/uri/escape/decode_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Escape#decode" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/escape/encode_spec.rb b/spec/ruby/library/uri/escape/encode_spec.rb new file mode 100644 index 0000000000..2b61b7c152 --- /dev/null +++ b/spec/ruby/library/uri/escape/encode_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Escape#encode" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/escape/escape_spec.rb b/spec/ruby/library/uri/escape/escape_spec.rb new file mode 100644 index 0000000000..dddbc60707 --- /dev/null +++ b/spec/ruby/library/uri/escape/escape_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Escape#escape" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/escape/unescape_spec.rb b/spec/ruby/library/uri/escape/unescape_spec.rb new file mode 100644 index 0000000000..7d574d13c1 --- /dev/null +++ b/spec/ruby/library/uri/escape/unescape_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Escape#unescape" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/extract_spec.rb b/spec/ruby/library/uri/extract_spec.rb new file mode 100644 index 0000000000..1294a480f1 --- /dev/null +++ b/spec/ruby/library/uri/extract_spec.rb @@ -0,0 +1,86 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.extract" do + it "behaves according to its documentation" do + URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.").should == ["http://foo.example.org/bla", "mailto:test@example.com"] + end + + it "treats contiguous URIs as a single URI" do + URI.extract('http://example.jphttp://example.jp').should == ['http://example.jphttp://example.jp'] + end + + it "treats pretty much anything with a colon as a URI" do + URI.extract('From: XXX [mailto:xxx@xxx.xxx.xxx]').should == ['From:', 'mailto:xxx@xxx.xxx.xxx]'] + end + + it "wraps a URI string in an array" do + URI.extract("http://github.com/brixen/rubyspec/tree/master").should == ["http://github.com/brixen/rubyspec/tree/master"] + end + + it "pulls a variety of protocol URIs from a string" do + URI.extract("this is a string, it has http://rubini.us/ in it").should == ["http://rubini.us/"] + URI.extract("mailto:spambait@example.com").should == ["mailto:spambait@example.com"] + URI.extract("ftp://ruby-lang.org/").should == ["ftp://ruby-lang.org/"] + URI.extract("https://mail.google.com").should == ["https://mail.google.com"] + URI.extract("anything://example.com/").should == ["anything://example.com/"] + end + + it "pulls all URIs within a string in order into an array when a block is not given" do + URI.extract("1.3. Example URI + + The following examples illustrate URI that are in common use. + + ftp://ftp.is.co.za/rfc/rfc1808.txt + -- ftp scheme for File Transfer Protocol services + + gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles + -- gopher scheme for Gopher and Gopher+ Protocol services + + http://www.math.uio.no/faq/compression-faq/part1.html + -- http scheme for Hypertext Transfer Protocol services + + mailto:mduerst@ifi.unizh.ch + -- mailto scheme for electronic mail addresses + + news:comp.infosystems.www.servers.unix + -- news scheme for USENET news groups and articles + + telnet://melvyl.ucop.edu/ + -- telnet scheme for interactive services via the TELNET Protocol + ").should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch","news:comp.infosystems.www.servers.unix","telnet://melvyl.ucop.edu/"] + end + + it "yields each URI in the given string in order to a block, if given, and returns nil" do + results = ["http://foo.example.org/bla", "mailto:test@example.com"] + URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") {|uri| + uri.should == results.shift + }.should == nil + results.should == [] + end + + it "allows the user to specify a list of acceptable protocols of URIs to scan for" do + URI.extract("1.3. Example URI + + The following examples illustrate URI that are in common use. + + ftp://ftp.is.co.za/rfc/rfc1808.txt + -- ftp scheme for File Transfer Protocol services + + gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles + -- gopher scheme for Gopher and Gopher+ Protocol services + + http://www.math.uio.no/faq/compression-faq/part1.html + -- http scheme for Hypertext Transfer Protocol services + + mailto:mduerst@ifi.unizh.ch + -- mailto scheme for electronic mail addresses + + news:comp.infosystems.www.servers.unix + -- news scheme for USENET news groups and articles + + telnet://melvyl.ucop.edu/ + -- telnet scheme for interactive services via the TELNET Protocol + ", ["http","ftp","mailto"]).should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch"] + end +end diff --git a/spec/ruby/library/uri/fixtures/classes.rb b/spec/ruby/library/uri/fixtures/classes.rb new file mode 100644 index 0000000000..e1179307cc --- /dev/null +++ b/spec/ruby/library/uri/fixtures/classes.rb @@ -0,0 +1,11 @@ +require 'uri' + +module URISpec + def self.components(uri) + result = {} + uri.component.each do |component| + result[component] = uri.send(component) + end + result + end +end diff --git a/spec/ruby/library/uri/fixtures/normalization.rb b/spec/ruby/library/uri/fixtures/normalization.rb new file mode 100644 index 0000000000..cbc26c9b48 --- /dev/null +++ b/spec/ruby/library/uri/fixtures/normalization.rb @@ -0,0 +1,54 @@ +module URISpec + # Not an exhaustive list. Refer to rfc3986 + NORMALIZED_FORMS = [ + { normalized: "http://example.com/", + equivalent: %w{ hTTp://example.com/ + http://exaMple.com/ + http://exa%4dple.com/ + http://exa%4Dple.com/ + http://exa%6dple.com/ + http://exa%6Dple.com/ + http://@example.com/ + http://example.com:/ + http://example.com:80/ + http://example.com + }, + different: %w{ http://example.com/# + http://example.com/? + http://example.com:8888/ + http:///example.com + http:example.com + https://example.com/ + }, + }, + { normalized: "http://example.com/index.html", + equivalent: %w{ http://example.com/index.ht%6dl + http://example.com/index.ht%6Dl + }, + different: %w{ http://example.com/index.hTMl + http://example.com/index.ht%4dl + http://example.com/index + http://example.com/ + http://example.com/ + }, + }, + { normalized: "http://example.com/x?y#z", + equivalent: %w{ http://example.com/x?y#%7a + http://example.com/x?y#%7A + http://example.com/x?%79#z + }, + different: %w{ http://example.com/x?Y#z + http://example.com/x?y#Z + http://example.com/x?y=#z + http://example.com/x?y + http://example.com/x#z + }, + }, + { normalized: "http://example.com/x?q=a%20b", + equivalent: %w{ + }, + different: %w{ http://example.com/x?q=a+b + }, + }, + ] +end diff --git a/spec/ruby/library/uri/ftp/build_spec.rb b/spec/ruby/library/uri/ftp/build_spec.rb new file mode 100644 index 0000000000..9e0fb44cf1 --- /dev/null +++ b/spec/ruby/library/uri/ftp/build_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::FTP.build" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ftp/merge_spec.rb b/spec/ruby/library/uri/ftp/merge_spec.rb new file mode 100644 index 0000000000..7a9997bbac --- /dev/null +++ b/spec/ruby/library/uri/ftp/merge_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::FTP#merge" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ftp/new2_spec.rb b/spec/ruby/library/uri/ftp/new2_spec.rb new file mode 100644 index 0000000000..eb1b149c81 --- /dev/null +++ b/spec/ruby/library/uri/ftp/new2_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::FTP.new2" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ftp/path_spec.rb b/spec/ruby/library/uri/ftp/path_spec.rb new file mode 100644 index 0000000000..5fec7f11b6 --- /dev/null +++ b/spec/ruby/library/uri/ftp/path_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::FTP#path=" do + before :each do + @url = URI.parse('ftp://example.com') + end + + it "does not require a leading /" do + @url.path = 'foo' + @url.path.should == 'foo' + end + + it "does not strip the leading /" do + @url.path = '/foo' + @url.path.should == '/foo' + end +end + +describe "URI::FTP#path" do + it "unescapes the leading /" do + url = URI.parse('ftp://example.com/%2Ffoo') + + url.path.should == '/foo' + end +end diff --git a/spec/ruby/library/uri/ftp/set_typecode_spec.rb b/spec/ruby/library/uri/ftp/set_typecode_spec.rb new file mode 100644 index 0000000000..31067930c0 --- /dev/null +++ b/spec/ruby/library/uri/ftp/set_typecode_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::FTP#set_typecode" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ftp/to_s_spec.rb b/spec/ruby/library/uri/ftp/to_s_spec.rb new file mode 100644 index 0000000000..3b4ff2d906 --- /dev/null +++ b/spec/ruby/library/uri/ftp/to_s_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' +require 'uri' + + +describe "URI::FTP#to_s" do + before :each do + @url = URI.parse('ftp://example.com') + end + + it "escapes the leading /" do + @url.path = '/foo' + + @url.to_s.should == 'ftp://example.com/%2Ffoo' + end +end diff --git a/spec/ruby/library/uri/ftp/typecode_spec.rb b/spec/ruby/library/uri/ftp/typecode_spec.rb new file mode 100644 index 0000000000..1f2bb02252 --- /dev/null +++ b/spec/ruby/library/uri/ftp/typecode_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::FTP#typecode" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::FTP#typecode=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/absolute_spec.rb b/spec/ruby/library/uri/generic/absolute_spec.rb new file mode 100644 index 0000000000..fe4b48d067 --- /dev/null +++ b/spec/ruby/library/uri/generic/absolute_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#absolute" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#absolute?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/build2_spec.rb b/spec/ruby/library/uri/generic/build2_spec.rb new file mode 100644 index 0000000000..9abd1d80ef --- /dev/null +++ b/spec/ruby/library/uri/generic/build2_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic.build2" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/build_spec.rb b/spec/ruby/library/uri/generic/build_spec.rb new file mode 100644 index 0000000000..50c27674ce --- /dev/null +++ b/spec/ruby/library/uri/generic/build_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic.build" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/coerce_spec.rb b/spec/ruby/library/uri/generic/coerce_spec.rb new file mode 100644 index 0000000000..f695e560ac --- /dev/null +++ b/spec/ruby/library/uri/generic/coerce_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#coerce" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/component_ary_spec.rb b/spec/ruby/library/uri/generic/component_ary_spec.rb new file mode 100644 index 0000000000..b39752f8d9 --- /dev/null +++ b/spec/ruby/library/uri/generic/component_ary_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#component_ary" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/component_spec.rb b/spec/ruby/library/uri/generic/component_spec.rb new file mode 100644 index 0000000000..f92409a0b0 --- /dev/null +++ b/spec/ruby/library/uri/generic/component_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#component" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic.component" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/default_port_spec.rb b/spec/ruby/library/uri/generic/default_port_spec.rb new file mode 100644 index 0000000000..4e10e34c9d --- /dev/null +++ b/spec/ruby/library/uri/generic/default_port_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#default_port" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic.default_port" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/eql_spec.rb b/spec/ruby/library/uri/generic/eql_spec.rb new file mode 100644 index 0000000000..df9987b524 --- /dev/null +++ b/spec/ruby/library/uri/generic/eql_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#eql?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/equal_value_spec.rb b/spec/ruby/library/uri/generic/equal_value_spec.rb new file mode 100644 index 0000000000..bd2feb86d4 --- /dev/null +++ b/spec/ruby/library/uri/generic/equal_value_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#==" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/fragment_spec.rb b/spec/ruby/library/uri/generic/fragment_spec.rb new file mode 100644 index 0000000000..20126b207a --- /dev/null +++ b/spec/ruby/library/uri/generic/fragment_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#fragment" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#fragment=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/hash_spec.rb b/spec/ruby/library/uri/generic/hash_spec.rb new file mode 100644 index 0000000000..286c1ab38d --- /dev/null +++ b/spec/ruby/library/uri/generic/hash_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#hash" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/hierarchical_spec.rb b/spec/ruby/library/uri/generic/hierarchical_spec.rb new file mode 100644 index 0000000000..df9bbae202 --- /dev/null +++ b/spec/ruby/library/uri/generic/hierarchical_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#hierarchical?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/host_spec.rb b/spec/ruby/library/uri/generic/host_spec.rb new file mode 100644 index 0000000000..4a5a162512 --- /dev/null +++ b/spec/ruby/library/uri/generic/host_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#host" do + # https://hackerone.com/reports/156615 + it "returns empty string when host is empty" do + URI.parse('http:////foo.com').host.should == '' + end +end + +describe "URI::Generic#host=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/inspect_spec.rb b/spec/ruby/library/uri/generic/inspect_spec.rb new file mode 100644 index 0000000000..4ff81eef82 --- /dev/null +++ b/spec/ruby/library/uri/generic/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#inspect" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/merge_spec.rb b/spec/ruby/library/uri/generic/merge_spec.rb new file mode 100644 index 0000000000..017873cc90 --- /dev/null +++ b/spec/ruby/library/uri/generic/merge_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#merge" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#merge!" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/minus_spec.rb b/spec/ruby/library/uri/generic/minus_spec.rb new file mode 100644 index 0000000000..ad8f816839 --- /dev/null +++ b/spec/ruby/library/uri/generic/minus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#-" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/normalize_spec.rb b/spec/ruby/library/uri/generic/normalize_spec.rb new file mode 100644 index 0000000000..d70a77c044 --- /dev/null +++ b/spec/ruby/library/uri/generic/normalize_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#normalize" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#normalize!" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/opaque_spec.rb b/spec/ruby/library/uri/generic/opaque_spec.rb new file mode 100644 index 0000000000..e6d40da52b --- /dev/null +++ b/spec/ruby/library/uri/generic/opaque_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#opaque" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#opaque=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/password_spec.rb b/spec/ruby/library/uri/generic/password_spec.rb new file mode 100644 index 0000000000..18db503883 --- /dev/null +++ b/spec/ruby/library/uri/generic/password_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#password" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#password=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/path_spec.rb b/spec/ruby/library/uri/generic/path_spec.rb new file mode 100644 index 0000000000..d84975c579 --- /dev/null +++ b/spec/ruby/library/uri/generic/path_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#path" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#path=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/plus_spec.rb b/spec/ruby/library/uri/generic/plus_spec.rb new file mode 100644 index 0000000000..e6d2222dac --- /dev/null +++ b/spec/ruby/library/uri/generic/plus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#+" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/port_spec.rb b/spec/ruby/library/uri/generic/port_spec.rb new file mode 100644 index 0000000000..6e5ef01493 --- /dev/null +++ b/spec/ruby/library/uri/generic/port_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#port" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#port=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/query_spec.rb b/spec/ruby/library/uri/generic/query_spec.rb new file mode 100644 index 0000000000..528cc3be02 --- /dev/null +++ b/spec/ruby/library/uri/generic/query_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#query" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#query=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/registry_spec.rb b/spec/ruby/library/uri/generic/registry_spec.rb new file mode 100644 index 0000000000..aece265a07 --- /dev/null +++ b/spec/ruby/library/uri/generic/registry_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#registry" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#registry=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/relative_spec.rb b/spec/ruby/library/uri/generic/relative_spec.rb new file mode 100644 index 0000000000..a7de1f306a --- /dev/null +++ b/spec/ruby/library/uri/generic/relative_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#relative?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/route_from_spec.rb b/spec/ruby/library/uri/generic/route_from_spec.rb new file mode 100644 index 0000000000..fd69816edf --- /dev/null +++ b/spec/ruby/library/uri/generic/route_from_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#route_from" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/route_to_spec.rb b/spec/ruby/library/uri/generic/route_to_spec.rb new file mode 100644 index 0000000000..7ab9aff2e8 --- /dev/null +++ b/spec/ruby/library/uri/generic/route_to_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#route_to" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/scheme_spec.rb b/spec/ruby/library/uri/generic/scheme_spec.rb new file mode 100644 index 0000000000..7922a8e977 --- /dev/null +++ b/spec/ruby/library/uri/generic/scheme_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#scheme" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#scheme=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/select_spec.rb b/spec/ruby/library/uri/generic/select_spec.rb new file mode 100644 index 0000000000..99aef83f99 --- /dev/null +++ b/spec/ruby/library/uri/generic/select_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#select" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_fragment_spec.rb b/spec/ruby/library/uri/generic/set_fragment_spec.rb new file mode 100644 index 0000000000..2476315f08 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_fragment_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_fragment" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_host_spec.rb b/spec/ruby/library/uri/generic/set_host_spec.rb new file mode 100644 index 0000000000..c7f5c6884e --- /dev/null +++ b/spec/ruby/library/uri/generic/set_host_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_host" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_opaque_spec.rb b/spec/ruby/library/uri/generic/set_opaque_spec.rb new file mode 100644 index 0000000000..8a494a7ee2 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_opaque_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_opaque" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_password_spec.rb b/spec/ruby/library/uri/generic/set_password_spec.rb new file mode 100644 index 0000000000..93b05fe911 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_password_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_password" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_path_spec.rb b/spec/ruby/library/uri/generic/set_path_spec.rb new file mode 100644 index 0000000000..6d9f59d1a5 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_path_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_path" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_port_spec.rb b/spec/ruby/library/uri/generic/set_port_spec.rb new file mode 100644 index 0000000000..2c8a4edd22 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_port_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_port" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_query_spec.rb b/spec/ruby/library/uri/generic/set_query_spec.rb new file mode 100644 index 0000000000..3f3453ba8e --- /dev/null +++ b/spec/ruby/library/uri/generic/set_query_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_query" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_registry_spec.rb b/spec/ruby/library/uri/generic/set_registry_spec.rb new file mode 100644 index 0000000000..44afe246d1 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_registry_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_registry" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_scheme_spec.rb b/spec/ruby/library/uri/generic/set_scheme_spec.rb new file mode 100644 index 0000000000..ffa29da446 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_scheme_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_scheme" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_user_spec.rb b/spec/ruby/library/uri/generic/set_user_spec.rb new file mode 100644 index 0000000000..9a39e1f4c3 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_user_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_user" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/set_userinfo_spec.rb b/spec/ruby/library/uri/generic/set_userinfo_spec.rb new file mode 100644 index 0000000000..76878204d2 --- /dev/null +++ b/spec/ruby/library/uri/generic/set_userinfo_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#set_userinfo" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/to_s_spec.rb b/spec/ruby/library/uri/generic/to_s_spec.rb new file mode 100644 index 0000000000..c436ee3c03 --- /dev/null +++ b/spec/ruby/library/uri/generic/to_s_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#to_s" do + # https://hackerone.com/reports/156615 + it "preserves / characters when host is empty" do + URI('http:///foo.com').to_s.should == 'http:///foo.com' + end +end diff --git a/spec/ruby/library/uri/generic/use_registry_spec.rb b/spec/ruby/library/uri/generic/use_registry_spec.rb new file mode 100644 index 0000000000..bdfe27c048 --- /dev/null +++ b/spec/ruby/library/uri/generic/use_registry_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic.use_registry" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/user_spec.rb b/spec/ruby/library/uri/generic/user_spec.rb new file mode 100644 index 0000000000..345412ca29 --- /dev/null +++ b/spec/ruby/library/uri/generic/user_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#user" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#user=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/generic/userinfo_spec.rb b/spec/ruby/library/uri/generic/userinfo_spec.rb new file mode 100644 index 0000000000..4bf111079c --- /dev/null +++ b/spec/ruby/library/uri/generic/userinfo_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Generic#userinfo" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::Generic#userinfo=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/http/build_spec.rb b/spec/ruby/library/uri/http/build_spec.rb new file mode 100644 index 0000000000..d34cf83ecf --- /dev/null +++ b/spec/ruby/library/uri/http/build_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::HTTP.build" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/http/request_uri_spec.rb b/spec/ruby/library/uri/http/request_uri_spec.rb new file mode 100644 index 0000000000..7b05147d36 --- /dev/null +++ b/spec/ruby/library/uri/http/request_uri_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::HTTP.request_uri" do + it "returns a string of the path + query" do + URI("http://reddit.com/r/ruby/").request_uri.should == "/r/ruby/" + URI("http://reddit.com/r/ruby/search?q=rubinius").request_uri.should == "/r/ruby/search?q=rubinius" + end + + it "returns '/' if the path of the URI is blank" do + URI("http://ruby.reddit.com").request_uri.should == "/" + end +end +describe "URI::HTTP#request_uri" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/join_spec.rb b/spec/ruby/library/uri/join_spec.rb new file mode 100644 index 0000000000..796f74134f --- /dev/null +++ b/spec/ruby/library/uri/join_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.join" do + it "returns a URI object of the concatenation of a protocol and domain, and a path" do + URI.join("http://localhost/","main.rbx").should == URI.parse("http://localhost/main.rbx") + end + + it "accepts URI objects" do + URI.join(URI("http://localhost/"),"main.rbx").should == URI.parse("http://localhost/main.rbx") + URI.join("http://localhost/",URI("main.rbx")).should == URI.parse("http://localhost/main.rbx") + URI.join(URI("http://localhost/"),URI("main.rbx")).should == URI.parse("http://localhost/main.rbx") + end + + it "accepts string-like arguments with to_str" do + str = mock('string-like') + str.should_receive(:to_str).and_return("http://ruby-lang.org") + str2 = mock('string-like also') + str2.should_receive(:to_str).and_return("foo/bar") + URI.join(str, str2).should == URI.parse("http://ruby-lang.org/foo/bar") + end + + it "raises an error if given no argument" do + -> { + URI.join + }.should raise_error(ArgumentError) + end + + it "doesn't create redundant '/'s" do + URI.join("http://localhost/", "/main.rbx").should == URI.parse("http://localhost/main.rbx") + end + + it "discards arguments given before an absolute uri" do + URI.join("http://localhost/a/b/c/d", "http://ruby-lang.com/foo", "bar").should == URI.parse("http://ruby-lang.com/bar") + end + + it "resolves .. in paths" do + URI.join("http://localhost/a/b/c/d", "../../e/f", "g/h/../i").to_s.should == "http://localhost/a/e/g/i" + end +end + + +# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo/bar')) +# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo', 'bar')) +# assert_equal(URI.parse('http://foo/bar/'), URI.join('http://foo', 'bar/')) +# +# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', 'baz')) +# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', '/baz')) +# assert_equal(URI.parse('http://foo/baz/'), URI.join('http://foo', 'bar', '/baz/')) +# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/', 'baz')) +# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar', 'baz', 'hoge')) +# +# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/baz')) +# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge')) +# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge')) +# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge')) +# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge')) +# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge')) +# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge')) diff --git a/spec/ruby/library/uri/ldap/attributes_spec.rb b/spec/ruby/library/uri/ldap/attributes_spec.rb new file mode 100644 index 0000000000..88e3328bad --- /dev/null +++ b/spec/ruby/library/uri/ldap/attributes_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#attributes" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::LDAP#attributes=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/build_spec.rb b/spec/ruby/library/uri/ldap/build_spec.rb new file mode 100644 index 0000000000..8d0e312d1a --- /dev/null +++ b/spec/ruby/library/uri/ldap/build_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP.build" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/dn_spec.rb b/spec/ruby/library/uri/ldap/dn_spec.rb new file mode 100644 index 0000000000..a5ac02e891 --- /dev/null +++ b/spec/ruby/library/uri/ldap/dn_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#dn" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::LDAP#dn=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/extensions_spec.rb b/spec/ruby/library/uri/ldap/extensions_spec.rb new file mode 100644 index 0000000000..473222eb7a --- /dev/null +++ b/spec/ruby/library/uri/ldap/extensions_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#extensions" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::LDAP#extensions=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/filter_spec.rb b/spec/ruby/library/uri/ldap/filter_spec.rb new file mode 100644 index 0000000000..d0b7fcc384 --- /dev/null +++ b/spec/ruby/library/uri/ldap/filter_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#filter" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::LDAP#filter=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/hierarchical_spec.rb b/spec/ruby/library/uri/ldap/hierarchical_spec.rb new file mode 100644 index 0000000000..5471c53d76 --- /dev/null +++ b/spec/ruby/library/uri/ldap/hierarchical_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#hierarchical?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/scope_spec.rb b/spec/ruby/library/uri/ldap/scope_spec.rb new file mode 100644 index 0000000000..5ea5581671 --- /dev/null +++ b/spec/ruby/library/uri/ldap/scope_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#scope" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::LDAP#scope=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/set_attributes_spec.rb b/spec/ruby/library/uri/ldap/set_attributes_spec.rb new file mode 100644 index 0000000000..fdaaa8344a --- /dev/null +++ b/spec/ruby/library/uri/ldap/set_attributes_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#set_attributes" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/set_dn_spec.rb b/spec/ruby/library/uri/ldap/set_dn_spec.rb new file mode 100644 index 0000000000..c50ee6a98d --- /dev/null +++ b/spec/ruby/library/uri/ldap/set_dn_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#set_dn" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/set_extensions_spec.rb b/spec/ruby/library/uri/ldap/set_extensions_spec.rb new file mode 100644 index 0000000000..5a39da4607 --- /dev/null +++ b/spec/ruby/library/uri/ldap/set_extensions_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#set_extensions" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/set_filter_spec.rb b/spec/ruby/library/uri/ldap/set_filter_spec.rb new file mode 100644 index 0000000000..c3ede20bb4 --- /dev/null +++ b/spec/ruby/library/uri/ldap/set_filter_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#set_filter" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/ldap/set_scope_spec.rb b/spec/ruby/library/uri/ldap/set_scope_spec.rb new file mode 100644 index 0000000000..43f3f68f86 --- /dev/null +++ b/spec/ruby/library/uri/ldap/set_scope_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::LDAP#set_scope" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/build_spec.rb b/spec/ruby/library/uri/mailto/build_spec.rb new file mode 100644 index 0000000000..2c011626ab --- /dev/null +++ b/spec/ruby/library/uri/mailto/build_spec.rb @@ -0,0 +1,92 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Mailto.build" do + it "conforms to the MatzRuby tests" do + ok = [] + bad = [] + + # RFC2368, 6. Examples + # mailto:chris@example.com + ok << ["mailto:chris@example.com"] + ok[-1] << ["chris@example.com", nil] + ok[-1] << {to: "chris@example.com"} + + # mailto:infobot@example.com?subject=current-issue + ok << ["mailto:infobot@example.com?subject=current-issue"] + ok[-1] << ["infobot@example.com", ["subject=current-issue"]] + ok[-1] << {to: "infobot@example.com", + headers: ["subject=current-issue"]} + + # mailto:infobot@example.com?body=send%20current-issue + ok << ["mailto:infobot@example.com?body=send%20current-issue"] + ok[-1] << ["infobot@example.com", ["body=send%20current-issue"]] + ok[-1] << {to: "infobot@example.com", + headers: ["body=send%20current-issue"]} + + # mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index + ok << ["mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index"] + ok[-1] << ["infobot@example.com", + ["body=send%20current-issue%0D%0Asend%20index"]] + ok[-1] << {to: "infobot@example.com", + headers: ["body=send%20current-issue%0D%0Asend%20index"]} + + # mailto:foobar@example.com?In-Reply-To=%3c3469A91.D10AF4C@example.com + ok << ["mailto:foobar@example.com?In-Reply-To=%3c3469A91.D10AF4C@example.com"] + ok[-1] << ["foobar@example.com", + ["In-Reply-To=%3c3469A91.D10AF4C@example.com"]] + ok[-1] << {to: "foobar@example.com", + headers: ["In-Reply-To=%3c3469A91.D10AF4C@example.com"]} + + # mailto:majordomo@example.com?body=subscribe%20bamboo-l + ok << ["mailto:majordomo@example.com?body=subscribe%20bamboo-l"] + ok[-1] << ["majordomo@example.com", ["body=subscribe%20bamboo-l"]] + ok[-1] << {to: "majordomo@example.com", + headers: ["body=subscribe%20bamboo-l"]} + + # mailto:joe@example.com?cc=bob@example.com&body=hello + ok << ["mailto:joe@example.com?cc=bob@example.com&body=hello"] + ok[-1] << ["joe@example.com", ["cc=bob@example.com", "body=hello"]] + ok[-1] << {to: "joe@example.com", + headers: ["cc=bob@example.com", "body=hello"]} + + # mailto:?to=joe@example.com&cc=bob@example.com&body=hello + ok << ["mailto:?to=joe@example.com&cc=bob@example.com&body=hello"] + ok[-1] << [nil, + ["to=joe@example.com", "cc=bob@example.com", "body=hello"]] + ok[-1] << {headers: ["to=joe@example.com", "cc=bob@example.com", "body=hello"]} + + # mailto:gorby%25kremvax@example.com + ok << ["mailto:gorby%25kremvax@example.com"] + ok[-1] << ["gorby%25kremvax@example.com", nil] + ok[-1] << {to: "gorby%25kremvax@example.com"} + + # mailto:unlikely%3Faddress@example.com?blat=foop + ok << ["mailto:unlikely%3Faddress@example.com?blat=foop"] + ok[-1] << ["unlikely%3Faddress@example.com", ["blat=foop"]] + ok[-1] << {to: "unlikely%3Faddress@example.com", + headers: ["blat=foop"]} + + ok_all = ok.flatten.join("\0") + + # mailto:joe@example.com?cc=bob@example.com?body=hello ; WRONG! + bad << ["joe@example.com", ["cc=bob@example.com?body=hello"]] + + # mailto:javascript:alert() + bad << ["javascript:alert()", []] + + # '=' which is in hname or hvalue is wrong. + bad << ["foo@example.jp?subject=1+1=2", []] + + ok.each do |x| + URI::MailTo.build(x[1]).to_s.should == x[0] + URI::MailTo.build(x[2]).to_s.should == x[0] + end + + bad.each do |x| + -> { URI::MailTo.build(x) }.should raise_error(URI::InvalidComponentError) + end + + ok.flatten.join("\0").should == ok_all + end +end diff --git a/spec/ruby/library/uri/mailto/headers_spec.rb b/spec/ruby/library/uri/mailto/headers_spec.rb new file mode 100644 index 0000000000..8aefec0e75 --- /dev/null +++ b/spec/ruby/library/uri/mailto/headers_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#headers" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::MailTo#headers=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/set_headers_spec.rb b/spec/ruby/library/uri/mailto/set_headers_spec.rb new file mode 100644 index 0000000000..b6ce1a694b --- /dev/null +++ b/spec/ruby/library/uri/mailto/set_headers_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#set_headers" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/set_to_spec.rb b/spec/ruby/library/uri/mailto/set_to_spec.rb new file mode 100644 index 0000000000..eabc47f9a8 --- /dev/null +++ b/spec/ruby/library/uri/mailto/set_to_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#set_to" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/to_mailtext_spec.rb b/spec/ruby/library/uri/mailto/to_mailtext_spec.rb new file mode 100644 index 0000000000..3763a2d402 --- /dev/null +++ b/spec/ruby/library/uri/mailto/to_mailtext_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#to_mailtext" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/to_rfc822text_spec.rb b/spec/ruby/library/uri/mailto/to_rfc822text_spec.rb new file mode 100644 index 0000000000..2843b46848 --- /dev/null +++ b/spec/ruby/library/uri/mailto/to_rfc822text_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#to_rfc822text" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/to_s_spec.rb b/spec/ruby/library/uri/mailto/to_s_spec.rb new file mode 100644 index 0000000000..746e8356eb --- /dev/null +++ b/spec/ruby/library/uri/mailto/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#to_s" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/mailto/to_spec.rb b/spec/ruby/library/uri/mailto/to_spec.rb new file mode 100644 index 0000000000..68dfadd359 --- /dev/null +++ b/spec/ruby/library/uri/mailto/to_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::MailTo#to" do + it "needs to be reviewed for spec completeness" +end + +describe "URI::MailTo#to=" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/merge_spec.rb b/spec/ruby/library/uri/merge_spec.rb new file mode 100644 index 0000000000..e9644a7fd0 --- /dev/null +++ b/spec/ruby/library/uri/merge_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI#merge" do + it "returns the receiver and the argument, joined as per URI.join" do + URI("http://localhost/").merge("main.rbx").should == URI.parse("http://localhost/main.rbx") + URI("http://localhost/a/b/c/d").merge("http://ruby-lang.com/foo").should == URI.parse("http://ruby-lang.com/foo") + URI("http://localhost/a/b/c/d").merge("../../e/f").to_s.should == "http://localhost/a/e/f" + end + + it "accepts URI objects as argument" do + URI("http://localhost/").merge(URI("main.rbx")).should == URI.parse("http://localhost/main.rbx") + end + + it "accepts a string-like argument" do + str = mock('string-like') + str.should_receive(:to_str).and_return("foo/bar") + URI("http://localhost/").merge(str).should == URI.parse("http://localhost/foo/bar") + end +end diff --git a/spec/ruby/library/uri/normalize_spec.rb b/spec/ruby/library/uri/normalize_spec.rb new file mode 100644 index 0000000000..3d4451990a --- /dev/null +++ b/spec/ruby/library/uri/normalize_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/normalization' +require 'uri' + +describe "URI#normalize" do + it "adds a / onto the end of the URI if the path is blank" do + no_path = URI("http://example.com") + no_path.to_s.should_not == "http://example.com/" + no_path.normalize.to_s.should == "http://example.com/" + end + + it "downcases the host of the URI" do + uri = URI("http://exAMPLE.cOm/") + uri.to_s.should_not == "http://example.com/" + uri.normalize.to_s.should == "http://example.com/" + end + + # The previous tests are included by the one below + + quarantine! do # Quarantined until redmine:2542 is accepted + it "respects RFC 3986" do + URISpec::NORMALIZED_FORMS.each do |form| + normal_uri = URI(form[:normalized]) + normalized = normal_uri.normalize.to_s + normal_uri.to_s.should == normalized + form[:equivalent].each do |same| + URI(same).normalize.to_s.should == normalized + end + form[:different].each do |other| + URI(other).normalize.to_s.should_not == normalized + end + end + end + end +end diff --git a/spec/ruby/library/uri/parse_spec.rb b/spec/ruby/library/uri/parse_spec.rb new file mode 100644 index 0000000000..e9ec59b490 --- /dev/null +++ b/spec/ruby/library/uri/parse_spec.rb @@ -0,0 +1,203 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "URI.parse" do + + it "returns a URI::HTTP object when parsing an HTTP URI" do + URI.parse("http://www.example.com/").should be_kind_of(URI::HTTP) + end + + it "populates the components of a parsed URI::HTTP, setting the port to 80 by default" do + # general case + URISpec.components(URI.parse("http://user:pass@example.com/path/?query=val&q2=val2#fragment")).should == { + scheme: "http", + userinfo: "user:pass", + host: "example.com", + port: 80, + path: "/path/", + query: "query=val&q2=val2", + fragment: "fragment" + } + + # multiple paths + URISpec.components(URI.parse("http://a/b/c/d;p?q")).should == { + scheme: "http", + userinfo: nil, + host: "a", + port: 80, + path: "/b/c/d;p", + query: "q", + fragment: nil + } + + # multi-level domain + URISpec.components(URI.parse('http://www.math.uio.no/faq/compression-faq/part1.html')).should == { + scheme: "http", + userinfo: nil, + host: "www.math.uio.no", + port: 80, + path: "/faq/compression-faq/part1.html", + query: nil, + fragment: nil + } + end + + it "parses out the port number of a URI, when given" do + URI.parse("http://example.com:8080/").port.should == 8080 + end + + it "returns a URI::HTTPS object when parsing an HTTPS URI" do + URI.parse("https://important-intern-net.net").should be_kind_of(URI::HTTPS) + end + + it "sets the port of a parsed https URI to 443 by default" do + URI.parse("https://example.com/").port.should == 443 + end + + it "populates the components of a parsed URI::FTP object" do + # generic, empty password. + url = URI.parse("ftp://anonymous@ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.bz2;type=i") + url.should be_kind_of(URI::FTP) + URISpec.components(url).should == { + scheme: "ftp", + userinfo: "anonymous", + host: "ruby-lang.org", + port: 21, + path: "pub/ruby/1.8/ruby-1.8.6.tar.bz2", + typecode: "i" + } + + # multidomain, no user or password + url = URI.parse('ftp://ftp.is.co.za/rfc/rfc1808.txt') + url.should be_kind_of(URI::FTP) + URISpec.components(url).should == { + scheme: "ftp", + userinfo: nil, + host: "ftp.is.co.za", + port: 21, + path: "rfc/rfc1808.txt", + typecode: nil + } + + # empty user + url = URI.parse('ftp://:pass@localhost/') + url.should be_kind_of(URI::FTP) + URISpec.components(url).should == { + scheme: "ftp", + userinfo: ":pass", + host: "localhost", + port: 21, + path: "", + typecode: nil + } + url.password.should == "pass" + end + + it "returns a URI::LDAP object when parsing an LDAP URI" do + #taken from http://www.faqs.org/rfcs/rfc2255.html 'cause I don't really know what an LDAP url looks like + ldap_uris = %w{ ldap:///o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen) ldap://ldap.itd.umich.edu/c=GB?objectClass?one ldap://ldap.question.com/o=Question%3f,c=US?mail ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04) ldap:///??sub??bindname=cn=Manager%2co=Foo ldap:///??sub??!bindname=cn=Manager%2co=Foo } + ldap_uris.each do |ldap_uri| + URI.parse(ldap_uri).should be_kind_of(URI::LDAP) + end + end + + it "populates the components of a parsed URI::LDAP object" do + URISpec.components(URI.parse("ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress?scope?filter?extensions")).should == { + scheme: "ldap", + host: "ldap.itd.umich.edu", + port: 389, + dn: "o=University%20of%20Michigan,c=US", + attributes: "postalAddress", + scope: "scope", + filter: "filter", + extensions: "extensions" + } + end + + it "returns a URI::MailTo object when passed a mailto URI" do + URI.parse("mailto:spam@mailinator.com").should be_kind_of(URI::MailTo) + end + + it "populates the components of a parsed URI::MailTo object" do + URISpec.components(URI.parse("mailto:spam@mailinator.com?subject=Discounts%20On%20Imported%20methods!!!&body=Exciting%20offer")).should == { + scheme: "mailto", + to: "spam@mailinator.com", + headers: [["subject","Discounts%20On%20Imported%20methods!!!"], + ["body", "Exciting%20offer"]] + } + end + + # TODO + # Test registry + it "does its best to extract components from URI::Generic objects" do + # generic + URISpec.components(URI("scheme://userinfo@host/path?query#fragment")).should == { + scheme: "scheme", + userinfo: "userinfo", + host: "host", + port: nil, + path: "/path", + query: "query", + fragment: "fragment", + registry: nil, + opaque: nil + } + + # gopher + gopher = URI.parse('gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles') + gopher.should be_kind_of(URI::Generic) + + URISpec.components(gopher).should == { + scheme: "gopher", + userinfo: nil, + host: "spinaltap.micro.umn.edu", + port: nil, + path: "/00/Weather/California/Los%20Angeles", + query: nil, + fragment: nil, + registry: nil, + opaque: nil + } + + # news + news = URI.parse('news:comp.infosystems.www.servers.unix') + news.should be_kind_of(URI::Generic) + URISpec.components(news).should == { + scheme: "news", + userinfo: nil, + host: nil, + port: nil, + path: nil, + query: nil, + fragment: nil, + registry: nil, + opaque: "comp.infosystems.www.servers.unix" + } + + # telnet + telnet = URI.parse('telnet://melvyl.ucop.edu/') + telnet.should be_kind_of(URI::Generic) + URISpec.components(telnet).should == { + scheme: "telnet", + userinfo: nil, + host: "melvyl.ucop.edu", + port: nil, + path: "/", + query: nil, + fragment: nil, + registry: nil, + opaque: nil + } + + # files + file_l = URI.parse('file:///foo/bar.txt') + file_l.should be_kind_of(URI::Generic) + file = URI.parse('file:/foo/bar.txt') + file.should be_kind_of(URI::Generic) + end + + it "doesn't raise errors on URIs which has underscore in reg_name" do + URI.parse('http://a_b:80/').host.should == "a_b" + URI.parse('http://a_b/').host.should == "a_b" + end +end diff --git a/spec/ruby/library/uri/parser/escape_spec.rb b/spec/ruby/library/uri/parser/escape_spec.rb new file mode 100644 index 0000000000..66853d9fcb --- /dev/null +++ b/spec/ruby/library/uri/parser/escape_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Parser#escape" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/parser/extract_spec.rb b/spec/ruby/library/uri/parser/extract_spec.rb new file mode 100644 index 0000000000..20d4565b08 --- /dev/null +++ b/spec/ruby/library/uri/parser/extract_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../shared/extract' +require 'uri' + +describe "URI::Parser#extract" do + it_behaves_like :uri_extract, :extract, URI::Parser.new +end diff --git a/spec/ruby/library/uri/parser/inspect_spec.rb b/spec/ruby/library/uri/parser/inspect_spec.rb new file mode 100644 index 0000000000..44fbd4077c --- /dev/null +++ b/spec/ruby/library/uri/parser/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Parser#split" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/parser/join_spec.rb b/spec/ruby/library/uri/parser/join_spec.rb new file mode 100644 index 0000000000..0c9230be76 --- /dev/null +++ b/spec/ruby/library/uri/parser/join_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../shared/join' +require 'uri' + +describe "URI::Parser#join" do + it_behaves_like :uri_join, :join, URI::Parser.new +end diff --git a/spec/ruby/library/uri/parser/make_regexp_spec.rb b/spec/ruby/library/uri/parser/make_regexp_spec.rb new file mode 100644 index 0000000000..0631d13ee6 --- /dev/null +++ b/spec/ruby/library/uri/parser/make_regexp_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Parser#make_regexp" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/parser/parse_spec.rb b/spec/ruby/library/uri/parser/parse_spec.rb new file mode 100644 index 0000000000..df126eab6d --- /dev/null +++ b/spec/ruby/library/uri/parser/parse_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/parse' + +describe "URI::Parser#parse" do + it_behaves_like :uri_parse, :parse, URI::Parser.new +end diff --git a/spec/ruby/library/uri/parser/split_spec.rb b/spec/ruby/library/uri/parser/split_spec.rb new file mode 100644 index 0000000000..44fbd4077c --- /dev/null +++ b/spec/ruby/library/uri/parser/split_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Parser#split" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/parser/unescape_spec.rb b/spec/ruby/library/uri/parser/unescape_spec.rb new file mode 100644 index 0000000000..e18d2eb9d3 --- /dev/null +++ b/spec/ruby/library/uri/parser/unescape_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Parser#unescape" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/plus_spec.rb b/spec/ruby/library/uri/plus_spec.rb new file mode 100644 index 0000000000..b84b0767c1 --- /dev/null +++ b/spec/ruby/library/uri/plus_spec.rb @@ -0,0 +1,459 @@ +require_relative '../../spec_helper' +require 'uri' + +#an alias of URI#merge +describe "URI#+" do + it "replaces the end of the path of the URI when added to a string that looks like a relative path" do + (URI('http://foo') + 'bar').should == URI("http://foo/bar") + (URI('http://foo/baz') + 'bar').should == URI("http://foo/bar") + (URI('http://foo/baz/') + 'bar').should == URI("http://foo/baz/bar") + (URI('mailto:foo@example.com') + "#bar").should == URI("mailto:foo@example.com#bar") + end + + it "replaces the entire path of the URI when added to a string that begins with a /" do + (URI('http://foo/baz/') + '/bar').should == URI("http://foo/bar") + end + + it "replaces the entire url when added to a string that looks like a full url" do + (URI.parse('http://a/b') + 'http://x/y').should == URI("http://x/y") + (URI.parse('telnet:example.com') + 'http://x/y').should == URI("http://x/y") + end + + it "canonicalizes the URI's path, removing ../'s" do + (URI.parse('http://a/b/c/../') + "./").should == URI("http://a/b/") + (URI.parse('http://a/b/c/../') + ".").should == URI("http://a/b/") + (URI.parse('http://a/b/c/') + "../").should == URI("http://a/b/") + (URI.parse('http://a/b/c/../../') + "./").should == URI("http://a/") + (URI.parse('http://a/b/c/') + "../e/").should == URI("http://a/b/e/") + (URI.parse('http://a/b/c/') + "../e/../").should == URI("http://a/b/") + (URI.parse('http://a/b/../c/') + ".").should == URI("http://a/c/") + + (URI.parse('http://a/b/c/../../../') + ".").should == URI("http://a/") + end + + it "doesn't canonicalize the path when adding to the empty string" do + (URI.parse('http://a/b/c/../') + "").should == URI("http://a/b/c/../") + end + + it "raises a URI::BadURIError when adding two relative URIs" do + -> {URI.parse('a/b/c') + "d"}.should raise_error(URI::BadURIError) + end + + #Todo: make more BDD? + it "conforms to the merge specifications from rfc 2396" do + @url = 'http://a/b/c/d;p?q' + @base_url = URI.parse(@url) + +# http://a/b/c/d;p?q +# g:h = g:h + url = @base_url.merge('g:h') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g:h' + url = @base_url.route_to('g:h') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g:h' + +# http://a/b/c/d;p?q +# g = http://a/b/c/g + url = @base_url.merge('g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g' + url = @base_url.route_to('http://a/b/c/g') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g' + +# http://a/b/c/d;p?q +# ./g = http://a/b/c/g + url = @base_url.merge('./g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g' + url = @base_url.route_to('http://a/b/c/g') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == './g' # ok + url.to_s.should == 'g' + +# http://a/b/c/d;p?q +# g/ = http://a/b/c/g/ + url = @base_url.merge('g/') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g/' + url = @base_url.route_to('http://a/b/c/g/') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g/' + +# http://a/b/c/d;p?q +# /g = http://a/g + url = @base_url.merge('/g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/g' + url = @base_url.route_to('http://a/g') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == '/g' # ok + url.to_s.should == '../../g' + +# http://a/b/c/d;p?q +# //g = http://g + url = @base_url.merge('//g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://g' + url = @base_url.route_to('http://g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '//g' + +# http://a/b/c/d;p?q +# ?y = http://a/b/c/?y + url = @base_url.merge('?y') + url.should be_kind_of(URI::HTTP) + + url.to_s.should == 'http://a/b/c/d;p?y' + + url = @base_url.route_to('http://a/b/c/?y') + url.should be_kind_of(URI::Generic) + url.to_s.should == '?y' + +# http://a/b/c/d;p?q +# g?y = http://a/b/c/g?y + url = @base_url.merge('g?y') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g?y' + url = @base_url.route_to('http://a/b/c/g?y') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g?y' + +# http://a/b/c/d;p?q +# #s = (current document)#s + url = @base_url.merge('#s') + url.should be_kind_of(URI::HTTP) + url.to_s.should == @base_url.to_s + '#s' + url = @base_url.route_to(@base_url.to_s + '#s') + url.should be_kind_of(URI::Generic) + url.to_s.should == '#s' + +# http://a/b/c/d;p?q +# g#s = http://a/b/c/g#s + url = @base_url.merge('g#s') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g#s' + url = @base_url.route_to('http://a/b/c/g#s') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g#s' + +# http://a/b/c/d;p?q +# g?y#s = http://a/b/c/g?y#s + url = @base_url.merge('g?y#s') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g?y#s' + url = @base_url.route_to('http://a/b/c/g?y#s') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g?y#s' + +# http://a/b/c/d;p?q +# ;x = http://a/b/c/;x + url = @base_url.merge(';x') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/;x' + url = @base_url.route_to('http://a/b/c/;x') + url.should be_kind_of(URI::Generic) + url.to_s.should == ';x' + +# http://a/b/c/d;p?q +# g;x = http://a/b/c/g;x + url = @base_url.merge('g;x') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g;x' + url = @base_url.route_to('http://a/b/c/g;x') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g;x' + +# http://a/b/c/d;p?q +# g;x?y#s = http://a/b/c/g;x?y#s + url = @base_url.merge('g;x?y#s') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g;x?y#s' + url = @base_url.route_to('http://a/b/c/g;x?y#s') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g;x?y#s' + +# http://a/b/c/d;p?q +# . = http://a/b/c/ + url = @base_url.merge('.') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/' + url = @base_url.route_to('http://a/b/c/') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == '.' # ok + url.to_s.should == './' + +# http://a/b/c/d;p?q +# ./ = http://a/b/c/ + url = @base_url.merge('./') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/' + url = @base_url.route_to('http://a/b/c/') + url.should be_kind_of(URI::Generic) + url.to_s.should == './' + +# http://a/b/c/d;p?q +# .. = http://a/b/ + url = @base_url.merge('..') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/' + url = @base_url.route_to('http://a/b/') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == '..' # ok + url.to_s.should == '../' + +# http://a/b/c/d;p?q +# ../ = http://a/b/ + url = @base_url.merge('../') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/' + url = @base_url.route_to('http://a/b/') + url.should be_kind_of(URI::Generic) + url.to_s.should == '../' + +# http://a/b/c/d;p?q +# ../g = http://a/b/g + url = @base_url.merge('../g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/g' + url = @base_url.route_to('http://a/b/g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '../g' + +# http://a/b/c/d;p?q +# ../.. = http://a/ + url = @base_url.merge('../..') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/' + url = @base_url.route_to('http://a/') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == '../..' # ok + url.to_s.should == '../../' + +# http://a/b/c/d;p?q +# ../../ = http://a/ + url = @base_url.merge('../../') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/' + url = @base_url.route_to('http://a/') + url.should be_kind_of(URI::Generic) + url.to_s.should == '../../' + +# http://a/b/c/d;p?q +# ../../g = http://a/g + url = @base_url.merge('../../g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/g' + url = @base_url.route_to('http://a/g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '../../g' + +# http://a/b/c/d;p?q +# <> = (current document) + url = @base_url.merge('') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/d;p?q' + url = @base_url.route_to('http://a/b/c/d;p?q') + url.should be_kind_of(URI::Generic) + url.to_s.should == '' + +# http://a/b/c/d;p?q +# /./g = http://a/./g + url = @base_url.merge('/./g') + url.should be_kind_of(URI::HTTP) + + url.to_s.should == 'http://a/g' + + url = @base_url.route_to('http://a/./g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '/./g' + +# http://a/b/c/d;p?q +# /../g = http://a/../g + url = @base_url.merge('/../g') + url.should be_kind_of(URI::HTTP) + + url.to_s.should == 'http://a/g' + + url = @base_url.route_to('http://a/../g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '/../g' + +# http://a/b/c/d;p?q +# g. = http://a/b/c/g. + url = @base_url.merge('g.') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g.' + url = @base_url.route_to('http://a/b/c/g.') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g.' + +# http://a/b/c/d;p?q +# .g = http://a/b/c/.g + url = @base_url.merge('.g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/.g' + url = @base_url.route_to('http://a/b/c/.g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '.g' + +# http://a/b/c/d;p?q +# g.. = http://a/b/c/g.. + url = @base_url.merge('g..') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g..' + url = @base_url.route_to('http://a/b/c/g..') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g..' + +# http://a/b/c/d;p?q +# ..g = http://a/b/c/..g + url = @base_url.merge('..g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/..g' + url = @base_url.route_to('http://a/b/c/..g') + url.should be_kind_of(URI::Generic) + url.to_s.should == '..g' + +# http://a/b/c/d;p?q +# ../../../g = http://a/../g + url = @base_url.merge('../../../g') + url.should be_kind_of(URI::HTTP) + + url.to_s.should == 'http://a/g' + + url = @base_url.route_to('http://a/../g') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == '../../../g' # ok? yes, it confuses you + url.to_s.should == '/../g' # and it is clearly + +# http://a/b/c/d;p?q +# ../../../../g = http://a/../../g + url = @base_url.merge('../../../../g') + url.should be_kind_of(URI::HTTP) + + url.to_s.should == 'http://a/g' + + url = @base_url.route_to('http://a/../../g') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == '../../../../g' # ok? yes, it confuses you + url.to_s.should == '/../../g' # and it is clearly + +# http://a/b/c/d;p?q +# ./../g = http://a/b/g + url = @base_url.merge('./../g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/g' + url = @base_url.route_to('http://a/b/g') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == './../g' # ok + url.to_s.should == '../g' + +# http://a/b/c/d;p?q +# ./g/. = http://a/b/c/g/ + url = @base_url.merge('./g/.') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g/' + url = @base_url.route_to('http://a/b/c/g/') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == './g/.' # ok + url.to_s.should == 'g/' + +# http://a/b/c/d;p?q +# g/./h = http://a/b/c/g/h + url = @base_url.merge('g/./h') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g/h' + url = @base_url.route_to('http://a/b/c/g/h') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == 'g/./h' # ok + url.to_s.should == 'g/h' + +# http://a/b/c/d;p?q +# g/../h = http://a/b/c/h + url = @base_url.merge('g/../h') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/h' + url = @base_url.route_to('http://a/b/c/h') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == 'g/../h' # ok + url.to_s.should == 'h' + +# http://a/b/c/d;p?q +# g;x=1/./y = http://a/b/c/g;x=1/y + url = @base_url.merge('g;x=1/./y') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g;x=1/y' + url = @base_url.route_to('http://a/b/c/g;x=1/y') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == 'g;x=1/./y' # ok + url.to_s.should == 'g;x=1/y' + +# http://a/b/c/d;p?q +# g;x=1/../y = http://a/b/c/y + url = @base_url.merge('g;x=1/../y') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/y' + url = @base_url.route_to('http://a/b/c/y') + url.should be_kind_of(URI::Generic) + url.to_s.should_not == 'g;x=1/../y' # ok + url.to_s.should == 'y' + +# http://a/b/c/d;p?q +# g?y/./x = http://a/b/c/g?y/./x + url = @base_url.merge('g?y/./x') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g?y/./x' + url = @base_url.route_to('http://a/b/c/g?y/./x') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g?y/./x' + +# http://a/b/c/d;p?q +# g?y/../x = http://a/b/c/g?y/../x + url = @base_url.merge('g?y/../x') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g?y/../x' + url = @base_url.route_to('http://a/b/c/g?y/../x') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g?y/../x' + +# http://a/b/c/d;p?q +# g#s/./x = http://a/b/c/g#s/./x + url = @base_url.merge('g#s/./x') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g#s/./x' + url = @base_url.route_to('http://a/b/c/g#s/./x') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g#s/./x' + +# http://a/b/c/d;p?q +# g#s/../x = http://a/b/c/g#s/../x + url = @base_url.merge('g#s/../x') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http://a/b/c/g#s/../x' + url = @base_url.route_to('http://a/b/c/g#s/../x') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'g#s/../x' + +# http://a/b/c/d;p?q +# http:g = http:g ; for validating parsers +# | http://a/b/c/g ; for backwards compatibility + url = @base_url.merge('http:g') + url.should be_kind_of(URI::HTTP) + url.to_s.should == 'http:g' + url = @base_url.route_to('http:g') + url.should be_kind_of(URI::Generic) + url.to_s.should == 'http:g' + end +end + +#TODO: incorporate these tests: +# +# u = URI.parse('http://foo/bar/baz') +# assert_equal(nil, u.merge!("")) +# assert_equal(nil, u.merge!(u)) +# assert(nil != u.merge!(".")) +# assert_equal('http://foo/bar/', u.to_s) +# assert(nil != u.merge!("../baz")) +# assert_equal('http://foo/baz', u.to_s) diff --git a/spec/ruby/library/uri/regexp_spec.rb b/spec/ruby/library/uri/regexp_spec.rb new file mode 100644 index 0000000000..6e8b3df4d0 --- /dev/null +++ b/spec/ruby/library/uri/regexp_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require 'uri' + +#I'm more or less ok with these limited tests, as the more extensive extract tests +#use URI.regexp +describe "URI.regexp" do + it "behaves according to the MatzRuby tests" do + URI.regexp.should == URI.regexp + 'x http:// x'.slice(URI.regexp).should == 'http://' + 'x http:// x'.slice(URI.regexp(['http'])).should == 'http://' + 'x http:// x ftp://'.slice(URI.regexp(['http'])).should == 'http://' + 'http://'.slice(URI.regexp([])).should == nil + ''.slice(URI.regexp).should == nil + 'xxxx'.slice(URI.regexp).should == nil + ':'.slice(URI.regexp).should == nil + 'From:'.slice(URI.regexp).should == 'From:' + end +end diff --git a/spec/ruby/library/uri/route_from_spec.rb b/spec/ruby/library/uri/route_from_spec.rb new file mode 100644 index 0000000000..501f455775 --- /dev/null +++ b/spec/ruby/library/uri/route_from_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI#route_from" do + + #this could be split out a good bit better + it "gives the minimal difference between the current URI and the target" do + URI("http://example.com/a.html").route_from('http://example.com/a.html').to_s.should == "" + URI("http://example.com/a.html").route_from('http://example.com/b.html').to_s.should == "a.html" + URI("http://example.com/a/").route_from('http://example.com/b/').to_s.should == "../a/" + URI("http://example.com/b/").route_from('http://example.com/a/c').to_s.should == "../b/" + URI("http://example.com/b/").route_from('http://example.com/a/b/').to_s.should == "../../b/" + URI("http://example.com/b/").route_from('http://EXAMPLE.cOm/a/b/').to_s.should == "../../b/" + URI("http://example.net/b/").route_from('http://example.com/a/b/').to_s.should == "//example.net/b/" + URI("mailto:foo@example.com#bar").route_from('mailto:foo@example.com').to_s.should == "#bar" + end + + it "accepts a string-like argument" do + str = mock('string-like') + str.should_receive(:to_str).and_return("http://example.com/b.html") + URI("http://example.com/a.html").route_from(str).to_s.should == "a.html" + end +end diff --git a/spec/ruby/library/uri/route_to_spec.rb b/spec/ruby/library/uri/route_to_spec.rb new file mode 100644 index 0000000000..ae9d38d23d --- /dev/null +++ b/spec/ruby/library/uri/route_to_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI#route_to" do + + #this could be split out a good bit better + it "gives the minimal difference between the current URI and the target" do + URI("http://example.com/a.html").route_to('http://example.com/a.html').to_s.should == "" + URI("http://example.com/a.html").route_to('http://example.com/b.html').to_s.should == "b.html" + URI("http://example.com/a/").route_to('http://example.com/b/').to_s.should == "../b/" + URI("http://example.com/a/c").route_to('http://example.com/b/').to_s.should == "../b/" + URI("http://example.com/a/b/").route_to('http://example.com/b/').to_s.should == "../../b/" + URI("http://example.com/a/b/").route_to('http://EXAMPLE.cOm/b/').to_s.should == "../../b/" + URI("http://example.com/a/b/").route_to('http://example.net/b/').to_s.should == "//example.net/b/" + URI("mailto:foo@example.com").route_to('mailto:foo@example.com#bar').to_s.should == "#bar" + + #this was a little surprising to me + URI("mailto:foo@example.com#bar").route_to('mailto:foo@example.com').to_s.should == "" + end + + it "accepts a string-like argument" do + str = mock('string-like') + str.should_receive(:to_str).and_return("http://example.com/b.html") + URI("http://example.com/a.html").route_to(str).to_s.should == "b.html" + end +end diff --git a/spec/ruby/library/uri/select_spec.rb b/spec/ruby/library/uri/select_spec.rb new file mode 100644 index 0000000000..839b68b3a1 --- /dev/null +++ b/spec/ruby/library/uri/select_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI#select" do + it "takes any number of component names as symbols, and returns an array of those components" do + URI("http://host:8080/path/").select.should == [] + URI("http://host:8080/path/").select(:scheme,:host,:port,:path).should == [ + "http","host",8080,"/path/"] + end + + it "returns nil for any valid component that isn't set and doesn't have a default" do + uri = URI("http://host") + uri.select(:userinfo, :query, :fragment).should == [nil] * 3 + uri.select(:port, :path).should == [80, ''] + end + + it "raises an ArgumentError if a component is requested that isn't valid under the given scheme" do + -> { URI("mailto:spam@mailinator.com").select(:path) }.should raise_error(ArgumentError) + -> { URI("http://blog.blag.web").select(:typecode) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if given strings rather than symbols" do + -> { + URI("http://host:8080/path/").select("scheme","host","port",'path') + }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/library/uri/set_component_spec.rb b/spec/ruby/library/uri/set_component_spec.rb new file mode 100644 index 0000000000..1d4165c535 --- /dev/null +++ b/spec/ruby/library/uri/set_component_spec.rb @@ -0,0 +1,47 @@ +require_relative '../../spec_helper' +require 'uri' + +#TODO: make this more BDD +describe "URI#select" do + it "conforms to the MatzRuby tests" do + uri = URI.parse('http://foo:bar@baz') + (uri.user = 'oof').should == 'oof' + version_is(URI::VERSION, "1.0.4") do + uri.to_s.should == 'http://oof@baz' + (uri.password = 'rab').should == 'rab' + uri.to_s.should == 'http://oof:rab@baz' + (uri.userinfo = 'foo').should == 'foo' + uri.to_s.should == 'http://foo@baz' + (uri.userinfo = ['foo', 'bar']).should == ['foo', 'bar'] + uri.to_s.should == 'http://foo:bar@baz' + (uri.userinfo = ['foo']).should == ['foo'] + uri.to_s.should == 'http://foo@baz' + (uri.host = 'zab').should == 'zab' + uri.to_s.should == 'http://zab' + (uri.port = 8080).should == 8080 + uri.to_s.should == 'http://zab:8080' + (uri.path = '/').should == '/' + uri.to_s.should == 'http://zab:8080/' + (uri.query = 'a=1').should == 'a=1' + uri.to_s.should == 'http://zab:8080/?a=1' + (uri.fragment = 'b123').should == 'b123' + uri.to_s.should == 'http://zab:8080/?a=1#b123' + end + + uri = URI.parse('http://example.com') + -> { uri.password = 'bar' }.should raise_error(URI::InvalidURIError) + uri.userinfo = 'foo:bar' + uri.to_s.should == 'http://foo:bar@example.com' + -> { uri.registry = 'bar' }.should raise_error(URI::InvalidURIError) + -> { uri.opaque = 'bar' }.should raise_error(URI::InvalidURIError) + + uri = URI.parse('mailto:foo@example.com') + -> { uri.user = 'bar' }.should raise_error(URI::InvalidURIError) + -> { uri.password = 'bar' }.should raise_error(URI::InvalidURIError) + -> { uri.userinfo = ['bar', 'baz'] }.should raise_error(URI::InvalidURIError) + -> { uri.host = 'bar' }.should raise_error(URI::InvalidURIError) + -> { uri.port = 'bar' }.should raise_error(URI::InvalidURIError) + -> { uri.path = 'bar' }.should raise_error(URI::InvalidURIError) + -> { uri.query = 'bar' }.should raise_error(URI::InvalidURIError) + end +end diff --git a/spec/ruby/library/uri/shared/eql.rb b/spec/ruby/library/uri/shared/eql.rb new file mode 100644 index 0000000000..2cc960d39a --- /dev/null +++ b/spec/ruby/library/uri/shared/eql.rb @@ -0,0 +1,17 @@ +describe :uri_eql, shared: true do + it "returns false if the normalized forms are different" do + URISpec::NORMALIZED_FORMS.each do |form| + normal_uri = URI(form[:normalized]) + form[:different].each do |other| + URI(other).send(@method, normal_uri).should be_false + end + end + end +end + +describe :uri_eql_against_other_types, shared: true do + it "returns false for when compared to non-uri objects" do + URI("http://example.com/").send(@method, "http://example.com/").should be_false + URI("http://example.com/").send(@method, nil).should be_false + end +end diff --git a/spec/ruby/library/uri/shared/extract.rb b/spec/ruby/library/uri/shared/extract.rb new file mode 100644 index 0000000000..efe60ae4b9 --- /dev/null +++ b/spec/ruby/library/uri/shared/extract.rb @@ -0,0 +1,83 @@ +describe :uri_extract, shared: true do + it "behaves according to its documentation" do + @object.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.").should == ["http://foo.example.org/bla", "mailto:test@example.com"] + end + + it "treats contiguous URIs as a single URI" do + @object.extract('http://example.jphttp://example.jp').should == ['http://example.jphttp://example.jp'] + end + + it "treats pretty much anything with a colon as a URI" do + @object.extract('From: XXX [mailto:xxx@xxx.xxx.xxx]').should == ['From:', 'mailto:xxx@xxx.xxx.xxx]'] + end + + it "wraps a URI string in an array" do + @object.extract("http://github.com/brixen/rubyspec/tree/master").should == ["http://github.com/brixen/rubyspec/tree/master"] + end + + it "pulls a variety of protocol URIs from a string" do + @object.extract("this is a string, it has http://rubini.us/ in it").should == ["http://rubini.us/"] + @object.extract("mailto:spambait@example.com").should == ["mailto:spambait@example.com"] + @object.extract("ftp://ruby-lang.org/").should == ["ftp://ruby-lang.org/"] + @object.extract("https://mail.google.com").should == ["https://mail.google.com"] + @object.extract("anything://example.com/").should == ["anything://example.com/"] + end + + it "pulls all URIs within a string in order into an array when a block is not given" do + @object.extract("1.3. Example URI + + The following examples illustrate URI that are in common use. + + ftp://ftp.is.co.za/rfc/rfc1808.txt + -- ftp scheme for File Transfer Protocol services + + gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles + -- gopher scheme for Gopher and Gopher+ Protocol services + + http://www.math.uio.no/faq/compression-faq/part1.html + -- http scheme for Hypertext Transfer Protocol services + + mailto:mduerst@ifi.unizh.ch + -- mailto scheme for electronic mail addresses + + news:comp.infosystems.www.servers.unix + -- news scheme for USENET news groups and articles + + telnet://melvyl.ucop.edu/ + -- telnet scheme for interactive services via the TELNET Protocol + ").should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch","news:comp.infosystems.www.servers.unix","telnet://melvyl.ucop.edu/"] + end + + it "yields each URI in the given string in order to a block, if given, and returns nil" do + results = ["http://foo.example.org/bla", "mailto:test@example.com"] + @object.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") {|uri| + uri.should == results.shift + }.should == nil + results.should == [] + end + + it "allows the user to specify a list of acceptable protocols of URIs to scan for" do + @object.extract("1.3. Example URI + + The following examples illustrate URI that are in common use. + + ftp://ftp.is.co.za/rfc/rfc1808.txt + -- ftp scheme for File Transfer Protocol services + + gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles + -- gopher scheme for Gopher and Gopher+ Protocol services + + http://www.math.uio.no/faq/compression-faq/part1.html + -- http scheme for Hypertext Transfer Protocol services + + mailto:mduerst@ifi.unizh.ch + -- mailto scheme for electronic mail addresses + + news:comp.infosystems.www.servers.unix + -- news scheme for USENET news groups and articles + + telnet://melvyl.ucop.edu/ + -- telnet scheme for interactive services via the TELNET Protocol + ", ["http","ftp","mailto"]).should == ["ftp://ftp.is.co.za/rfc/rfc1808.txt","http://www.math.uio.no/faq/compression-faq/part1.html","mailto:mduerst@ifi.unizh.ch"] + end +end diff --git a/spec/ruby/library/uri/shared/join.rb b/spec/ruby/library/uri/shared/join.rb new file mode 100644 index 0000000000..4df0782b37 --- /dev/null +++ b/spec/ruby/library/uri/shared/join.rb @@ -0,0 +1,56 @@ +describe :uri_join, shared: true do + it "returns a URI object of the concatenation of a protocol and domain, and a path" do + @object.join("http://localhost/","main.rbx").should == URI.parse("http://localhost/main.rbx") + end + + it "accepts URI objects" do + @object.join(URI("http://localhost/"),"main.rbx").should == URI.parse("http://localhost/main.rbx") + @object.join("http://localhost/",URI("main.rbx")).should == URI.parse("http://localhost/main.rbx") + @object.join(URI("http://localhost/"),URI("main.rbx")).should == URI.parse("http://localhost/main.rbx") + end + + it "accepts string-like arguments with to_str" do + str = mock('string-like') + str.should_receive(:to_str).and_return("http://ruby-lang.org") + str2 = mock('string-like also') + str2.should_receive(:to_str).and_return("foo/bar") + @object.join(str, str2).should == URI.parse("http://ruby-lang.org/foo/bar") + end + + it "raises an error if given no argument" do + -> { + @object.join + }.should raise_error(ArgumentError) + end + + it "doesn't create redundant '/'s" do + @object.join("http://localhost/", "/main.rbx").should == URI.parse("http://localhost/main.rbx") + end + + it "discards arguments given before an absolute uri" do + @object.join("http://localhost/a/b/c/d", "http://ruby-lang.com/foo", "bar").should == URI.parse("http://ruby-lang.com/bar") + end + + it "resolves .. in paths" do + @object.join("http://localhost/a/b/c/d", "../../e/f", "g/h/../i").to_s.should == "http://localhost/a/e/g/i" + end +end + + +# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo/bar')) +# assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo', 'bar')) +# assert_equal(URI.parse('http://foo/bar/'), URI.join('http://foo', 'bar/')) +# +# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', 'baz')) +# assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', '/baz')) +# assert_equal(URI.parse('http://foo/baz/'), URI.join('http://foo', 'bar', '/baz/')) +# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/', 'baz')) +# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar', 'baz', 'hoge')) +# +# assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/baz')) +# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge')) +# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge')) +# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge')) +# assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge')) +# assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge')) +# assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge')) diff --git a/spec/ruby/library/uri/shared/parse.rb b/spec/ruby/library/uri/shared/parse.rb new file mode 100644 index 0000000000..c5057b6c4b --- /dev/null +++ b/spec/ruby/library/uri/shared/parse.rb @@ -0,0 +1,206 @@ +describe :uri_parse, shared: true do + it "returns a URI::HTTP object when parsing an HTTP URI" do + @object.parse("http://www.example.com/").should be_kind_of(URI::HTTP) + end + + it "populates the components of a parsed URI::HTTP, setting the port to 80 by default" do + # general case + URISpec.components(@object.parse("http://user:pass@example.com/path/?query=val&q2=val2#fragment")).should == { + scheme: "http", + userinfo: "user:pass", + host: "example.com", + port: 80, + path: "/path/", + query: "query=val&q2=val2", + fragment: "fragment" + } + + # multiple paths + URISpec.components(@object.parse("http://a/b/c/d;p?q")).should == { + scheme: "http", + userinfo: nil, + host: "a", + port: 80, + path: "/b/c/d;p", + query: "q", + fragment: nil + } + + # multi-level domain + URISpec.components(@object.parse('http://www.math.uio.no/faq/compression-faq/part1.html')).should == { + scheme: "http", + userinfo: nil, + host: "www.math.uio.no", + port: 80, + path: "/faq/compression-faq/part1.html", + query: nil, + fragment: nil + } + end + + it "parses out the port number of a URI, when given" do + @object.parse("http://example.com:8080/").port.should == 8080 + end + + it "returns a URI::HTTPS object when parsing an HTTPS URI" do + @object.parse("https://important-intern-net.net").should be_kind_of(URI::HTTPS) + end + + it "sets the port of a parsed https URI to 443 by default" do + @object.parse("https://example.com/").port.should == 443 + end + + it "populates the components of a parsed URI::FTP object" do + # generic, empty password. + url = @object.parse("ftp://anonymous@ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.bz2;type=i") + url.should be_kind_of(URI::FTP) + URISpec.components(url).should == { + scheme: "ftp", + userinfo: "anonymous", + host: "ruby-lang.org", + port: 21, + path: "pub/ruby/1.8/ruby-1.8.6.tar.bz2", + typecode: "i" + } + + # multidomain, no user or password + url = @object.parse('ftp://ftp.is.co.za/rfc/rfc1808.txt') + url.should be_kind_of(URI::FTP) + URISpec.components(url).should == { + scheme: "ftp", + userinfo: nil, + host: "ftp.is.co.za", + port: 21, + path: "rfc/rfc1808.txt", + typecode: nil + } + + # empty user + url = @object.parse('ftp://:pass@localhost/') + url.should be_kind_of(URI::FTP) + URISpec.components(url).should == { + scheme: "ftp", + userinfo: ":pass", + host: "localhost", + port: 21, + path: "", + typecode: nil + } + url.password.should == "pass" + end + + it "returns a URI::LDAP object when parsing an LDAP URI" do + #taken from http://www.faqs.org/rfcs/rfc2255.html 'cause I don't really know what an LDAP url looks like + ldap_uris = %w{ ldap:///o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen) ldap://ldap.itd.umich.edu/c=GB?objectClass?one ldap://ldap.question.com/o=Question%3f,c=US?mail ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04) ldap:///??sub??bindname=cn=Manager%2co=Foo ldap:///??sub??!bindname=cn=Manager%2co=Foo } + ldap_uris.each do |ldap_uri| + @object.parse(ldap_uri).should be_kind_of(URI::LDAP) + end + end + + it "populates the components of a parsed URI::LDAP object" do + URISpec.components(@object.parse("ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress?scope?filter?extensions")).should == { + scheme: "ldap", + host: "ldap.itd.umich.edu", + port: 389, + dn: "o=University%20of%20Michigan,c=US", + attributes: "postalAddress", + scope: "scope", + filter: "filter", + extensions: "extensions" + } + end + + it "returns a URI::MailTo object when passed a mailto URI" do + @object.parse("mailto:spam@mailinator.com").should be_kind_of(URI::MailTo) + end + + it "populates the components of a parsed URI::MailTo object" do + URISpec.components(@object.parse("mailto:spam@mailinator.com?subject=Discounts%20On%20Imported%20methods!!!&body=Exciting%20offer")).should == { + scheme: "mailto", + to: "spam@mailinator.com", + headers: [["subject","Discounts%20On%20Imported%20methods!!!"], + ["body", "Exciting%20offer"]] + } + end + + # TODO + # Test registry + it "does its best to extract components from URI::Generic objects" do + # generic + URISpec.components(URI("scheme://userinfo@host/path?query#fragment")).should == { + scheme: "scheme", + userinfo: "userinfo", + host: "host", + port: nil, + path: "/path", + query: "query", + fragment: "fragment", + registry: nil, + opaque: nil + } + + # gopher + gopher = @object.parse('gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles') + gopher.should be_kind_of(URI::Generic) + + URISpec.components(gopher).should == { + scheme: "gopher", + userinfo: nil, + host: "spinaltap.micro.umn.edu", + port: nil, + path: "/00/Weather/California/Los%20Angeles", + query: nil, + fragment: nil, + registry: nil, + opaque: nil + } + + # news + news = @object.parse('news:comp.infosystems.www.servers.unix') + news.should be_kind_of(URI::Generic) + URISpec.components(news).should == { + scheme: "news", + userinfo: nil, + host: nil, + port: nil, + path: nil, + query: nil, + fragment: nil, + registry: nil, + opaque: "comp.infosystems.www.servers.unix" + } + + # telnet + telnet = @object.parse('telnet://melvyl.ucop.edu/') + telnet.should be_kind_of(URI::Generic) + URISpec.components(telnet).should == { + scheme: "telnet", + userinfo: nil, + host: "melvyl.ucop.edu", + port: nil, + path: "/", + query: nil, + fragment: nil, + registry: nil, + opaque: nil + } + + # files + file_l = @object.parse('file:///foo/bar.txt') + file_l.should be_kind_of(URI::Generic) + file = @object.parse('file:/foo/bar.txt') + file.should be_kind_of(URI::Generic) + end + + if URI::DEFAULT_PARSER == URI::RFC2396_Parser + it "raises errors on malformed URIs" do + -> { @object.parse('http://a_b:80/') }.should raise_error(URI::InvalidURIError) + -> { @object.parse('http://a_b/') }.should raise_error(URI::InvalidURIError) + end + elsif URI::DEFAULT_PARSER == URI::RFC3986_Parser + it "does not raise errors on URIs contained underscore" do + -> { @object.parse('http://a_b:80/') }.should_not raise_error(URI::InvalidURIError) + -> { @object.parse('http://a_b/') }.should_not raise_error(URI::InvalidURIError) + end + end +end diff --git a/spec/ruby/library/uri/split_spec.rb b/spec/ruby/library/uri/split_spec.rb new file mode 100644 index 0000000000..9ad37e3b1f --- /dev/null +++ b/spec/ruby/library/uri/split_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require 'uri' + +describe "URI.split" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/uri/uri_spec.rb b/spec/ruby/library/uri/uri_spec.rb new file mode 100644 index 0000000000..45a7502052 --- /dev/null +++ b/spec/ruby/library/uri/uri_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require 'uri' + +#the testing is light here as this is an alias for URI.parse + +#we're just testing that the method ends up in the right place +describe "the URI method" do + it "parses a given URI, returning a URI object" do + result = URI.parse("http://ruby-lang.org") + URI("http://ruby-lang.org").should == result + Kernel::URI("http://ruby-lang.org").should == result + end + + it "converts its argument with to_str" do + str = mock('string-like') + str.should_receive(:to_str).and_return("http://ruby-lang.org") + URI(str).should == URI.parse("http://ruby-lang.org") + end + + it "returns the argument if it is a URI object" do + result = URI.parse("http://ruby-lang.org") + URI(result).should equal(result) + end + + #apparently this was a concern? imported from MRI tests + it "does not add a URI method to Object instances" do + -> {Object.new.URI("http://ruby-lang.org/")}.should raise_error(NoMethodError) + end +end diff --git a/spec/ruby/library/uri/util/make_components_hash_spec.rb b/spec/ruby/library/uri/util/make_components_hash_spec.rb new file mode 100644 index 0000000000..6d26b81130 --- /dev/null +++ b/spec/ruby/library/uri/util/make_components_hash_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require 'uri' + +describe "URI::Util.make_components_hash" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/library/weakref/__getobj___spec.rb b/spec/ruby/library/weakref/__getobj___spec.rb new file mode 100644 index 0000000000..79b06f5c96 --- /dev/null +++ b/spec/ruby/library/weakref/__getobj___spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "WeakRef#__getobj__" do + it "returns the object if it is reachable" do + obj = Object.new + ref = WeakRef.new(obj) + ref.__getobj__.should equal(obj) + end + + it "raises WeakRef::RefError if the object is no longer reachable" do + ref = WeakRefSpec.make_dead_weakref + -> { + ref.__getobj__ + }.should raise_error(WeakRef::RefError) + end +end diff --git a/spec/ruby/library/weakref/allocate_spec.rb b/spec/ruby/library/weakref/allocate_spec.rb new file mode 100644 index 0000000000..e734cfd23d --- /dev/null +++ b/spec/ruby/library/weakref/allocate_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'weakref' + +describe "WeakRef#allocate" do + it "assigns nil as the reference" do + -> { WeakRef.allocate.__getobj__ }.should raise_error(WeakRef::RefError) + end +end diff --git a/spec/ruby/library/weakref/fixtures/classes.rb b/spec/ruby/library/weakref/fixtures/classes.rb new file mode 100644 index 0000000000..041afab14d --- /dev/null +++ b/spec/ruby/library/weakref/fixtures/classes.rb @@ -0,0 +1,26 @@ +require 'weakref' + +# From MRI test_weakref.rb +class WeakRefSpec + def self.make_weakref(level = 10) + if level > 0 + make_weakref(level - 1) + else + WeakRef.new(Object.new) + end + end + + def self.make_dead_weakref + weaks = [] + weak = nil + 1000.times do + weaks << make_weakref + end + + 1000.times do + GC.start + break if weak = weaks.find { |w| !w.weakref_alive? } + end + weak + end +end diff --git a/spec/ruby/library/weakref/new_spec.rb b/spec/ruby/library/weakref/new_spec.rb new file mode 100644 index 0000000000..6290e61fe3 --- /dev/null +++ b/spec/ruby/library/weakref/new_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'weakref' + +describe "WeakRef#new" do + it "creates a subclass correctly" do + wr2 = Class.new(WeakRef) { + def __getobj__ + :dummy + end + } + wr2.new(Object.new).__getobj__.should == :dummy + end +end diff --git a/spec/ruby/library/weakref/send_spec.rb b/spec/ruby/library/weakref/send_spec.rb new file mode 100644 index 0000000000..9591657e01 --- /dev/null +++ b/spec/ruby/library/weakref/send_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' +require 'weakref' + +describe "WeakRef#__send__" do + module WeakRefSpecs + class << self + def delegated_method + :result + end + + def protected_method + :result + end + protected :protected_method + + def private_method + :result + end + private :private_method + end + end + + it "delegates to public methods of the weakly-referenced object" do + wr = WeakRef.new(WeakRefSpecs) + wr.delegated_method.should == :result + end + + it "delegates to protected methods of the weakly-referenced object" do + wr = WeakRef.new(WeakRefSpecs) + -> { wr.protected_method }.should raise_error(NameError) + end + + it "does not delegate to private methods of the weakly-referenced object" do + wr = WeakRef.new(WeakRefSpecs) + -> { wr.private_method }.should raise_error(NameError) + end +end diff --git a/spec/ruby/library/weakref/weakref_alive_spec.rb b/spec/ruby/library/weakref/weakref_alive_spec.rb new file mode 100644 index 0000000000..1ebf9c1ee3 --- /dev/null +++ b/spec/ruby/library/weakref/weakref_alive_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "WeakRef#weakref_alive?" do + it "returns true if the object is reachable" do + obj = Object.new + ref = WeakRef.new(obj) + ref.weakref_alive?.should be_true + end + + it "returns a falsy value if the object is no longer reachable" do + ref = WeakRefSpec.make_dead_weakref + [false, nil].should include(ref.weakref_alive?) + end +end diff --git a/spec/ruby/library/win32ole/fixtures/classes.rb b/spec/ruby/library/win32ole/fixtures/classes.rb new file mode 100644 index 0000000000..5a16fcca45 --- /dev/null +++ b/spec/ruby/library/win32ole/fixtures/classes.rb @@ -0,0 +1,33 @@ +require 'win32ole' + +# win32ole deprecated constants like WIN32OLE_TYPELIB in Ruby 3.4 +# but only added the replacements like WIN32OLE::TypeLib in Ruby 3.4. +# So we use the new-style constants in specs to avoid deprecation warnings +# and we define the new-style constants as the old ones if they don't exist yet. +WIN32OLE::TypeLib ||= WIN32OLE_TYPELIB +WIN32OLE::RuntimeError ||= WIN32OLERuntimeError +WIN32OLE::Method ||= WIN32OLE_METHOD +WIN32OLE::Type ||= WIN32OLE_TYPE +WIN32OLE::Event ||= WIN32OLE_EVENT +WIN32OLE::Param ||= WIN32OLE_PARAM + +module WIN32OLESpecs + MSXML_AVAILABLE = WIN32OLE::TypeLib.typelibs.any? { |t| t.name.start_with?('Microsoft XML') } + SYSTEM_MONITOR_CONTROL_AVAILABLE = WIN32OLE::TypeLib.typelibs.any? { |t| t.name.start_with?('System Monitor Control') } + + def self.new_ole(name) + tries = 0 + begin + WIN32OLE.new(name) + rescue WIN32OLE::RuntimeError => e + if tries < 3 + tries += 1 + $stderr.puts "WIN32OLESpecs#new_ole retry (#{tries}): #{e.class}: #{e.message}" + sleep(2 ** tries) + retry + else + raise + end + end + end +end diff --git a/spec/ruby/library/win32ole/fixtures/event.xml b/spec/ruby/library/win32ole/fixtures/event.xml new file mode 100644 index 0000000000..23f3d2b126 --- /dev/null +++ b/spec/ruby/library/win32ole/fixtures/event.xml @@ -0,0 +1,4 @@ +<program> + <name>Ruby</name> + <version>trunk</version> +</program> diff --git a/spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb b/spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb new file mode 100644 index 0000000000..52cb978bea --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb @@ -0,0 +1,15 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#_getproperty" do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "gets value" do + @dict.add('key', 'value') + @dict._getproperty(0, ['key'], [WIN32OLE::VARIANT::VT_BSTR]).should == 'value' + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/_invoke_spec.rb b/spec/ruby/library/win32ole/win32ole/_invoke_spec.rb new file mode 100644 index 0000000000..994c2e6d36 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/_invoke_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#_invoke" do + before :each do + @shell = WIN32OLESpecs.new_ole 'Shell.application' + end + + it "raises ArgumentError if insufficient number of arguments are given" do + -> { @shell._invoke() }.should raise_error ArgumentError + -> { @shell._invoke(0) }.should raise_error ArgumentError + -> { @shell._invoke(0, []) }.should raise_error ArgumentError + end + + it "dispatches the method bound to a specific ID" do + @shell._invoke(0x60020002, [37], [WIN32OLE::VARIANT::VT_VARIANT]).title.should =~ /System32/i + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/codepage_spec.rb b/spec/ruby/library/win32ole/win32ole/codepage_spec.rb new file mode 100644 index 0000000000..07e93646ac --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/codepage_spec.rb @@ -0,0 +1,14 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE.codepage=" do + it "sets codepage" do + cp = WIN32OLE.codepage + WIN32OLE.codepage = WIN32OLE::CP_UTF8 + WIN32OLE.codepage.should == WIN32OLE::CP_UTF8 + WIN32OLE.codepage = cp + end + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/connect_spec.rb b/spec/ruby/library/win32ole/win32ole/connect_spec.rb new file mode 100644 index 0000000000..ac0976ddc1 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/connect_spec.rb @@ -0,0 +1,16 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE.connect" do + it "creates WIN32OLE object given valid argument" do + obj = WIN32OLE.connect("winmgmts:") + obj.should be_kind_of WIN32OLE + end + + it "raises TypeError when given invalid argument" do + -> { WIN32OLE.connect 1 }.should raise_error TypeError + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole/const_load_spec.rb b/spec/ruby/library/win32ole/win32ole/const_load_spec.rb new file mode 100644 index 0000000000..2099c4aa66 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/const_load_spec.rb @@ -0,0 +1,33 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE.const_load when passed Shell.Application OLE object" do + before :each do + @win32ole = WIN32OLESpecs.new_ole 'Shell.Application' + end + + it "loads constant SsfWINDOWS into WIN32OLE namespace" do + WIN32OLE.const_defined?(:SsfWINDOWS).should be_false + WIN32OLE.const_load @win32ole + WIN32OLE.const_defined?(:SsfWINDOWS).should be_true + end + end + + describe "WIN32OLE.const_load when namespace is specified" do + before :each do + module WIN32OLE_RUBYSPEC; end + @win32ole = WIN32OLESpecs.new_ole 'Shell.Application' + end + + it "loads constants into given namespace" do + module WIN32OLE_RUBYSPEC; end + + WIN32OLE_RUBYSPEC.const_defined?(:SsfWINDOWS).should be_false + WIN32OLE.const_load @win32ole, WIN32OLE_RUBYSPEC + WIN32OLE_RUBYSPEC.const_defined?(:SsfWINDOWS).should be_true + + end + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/constants_spec.rb b/spec/ruby/library/win32ole/win32ole/constants_spec.rb new file mode 100644 index 0000000000..8533741440 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/constants_spec.rb @@ -0,0 +1,43 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE class" do + it "defines constant CP_ACP" do + WIN32OLE::CP_ACP.should == 0 + end + + it "defines constant CP_OEMCP" do + WIN32OLE::CP_OEMCP.should == 1 + end + + it "defines constant CP_MACCP" do + WIN32OLE::CP_MACCP.should == 2 + end + + it "defines constant CP_THREAD_ACP" do + WIN32OLE::CP_THREAD_ACP.should == 3 + end + + it "defines constant CP_SYMBOL" do + WIN32OLE::CP_SYMBOL.should == 42 + end + + it "defines constant CP_UTF7" do + WIN32OLE::CP_UTF7.should == 65000 + end + + it "defines constant CP_UTF8" do + WIN32OLE::CP_UTF8.should == 65001 + end + + it "defines constant LOCALE_SYSTEM_DEFAULT" do + WIN32OLE::LOCALE_SYSTEM_DEFAULT.should == 0x0800 + end + + it "defines constant LOCALE_USER_DEFAULT" do + WIN32OLE::LOCALE_USER_DEFAULT.should == 0x0400 + end + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/create_guid_spec.rb b/spec/ruby/library/win32ole/win32ole/create_guid_spec.rb new file mode 100644 index 0000000000..8aa853df9e --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/create_guid_spec.rb @@ -0,0 +1,10 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE.create_guid" do + it "generates guid with valid format" do + WIN32OLE.create_guid.should =~ /^\{[A-Z0-9]{8}\-[A-Z0-9]{4}\-[A-Z0-9]{4}\-[A-Z0-9]{4}\-[A-Z0-9]{12}/ + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/invoke_spec.rb b/spec/ruby/library/win32ole/win32ole/invoke_spec.rb new file mode 100644 index 0000000000..d6ff7fade3 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/invoke_spec.rb @@ -0,0 +1,15 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#invoke" do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "get value by invoking 'Item' OLE method" do + @dict.add('key', 'value') + @dict.invoke('Item', 'key').should == 'value' + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/locale_spec.rb b/spec/ruby/library/win32ole/win32ole/locale_spec.rb new file mode 100644 index 0000000000..89e84d8038 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/locale_spec.rb @@ -0,0 +1,30 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE.locale" do + it "gets locale" do + WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT + end + end + + describe "WIN32OLE.locale=" do + it "sets locale to Japanese, if available" do + begin + begin + WIN32OLE.locale = 1041 + rescue WIN32OLE::RuntimeError + STDERR.puts("\n#{__FILE__}:#{__LINE__}:#{self.class.name}.test_s_locale_set is skipped(Japanese locale is not installed)") + return + end + + WIN32OLE.locale.should == 1041 + WIN32OLE.locale = WIN32OLE::LOCALE_SYSTEM_DEFAULT + -> { WIN32OLE.locale = 111 }.should raise_error WIN32OLE::RuntimeError + WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT + ensure + WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT + end + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/new_spec.rb b/spec/ruby/library/win32ole/win32ole/new_spec.rb new file mode 100644 index 0000000000..b2a0a5da18 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/new_spec.rb @@ -0,0 +1,26 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLESpecs.new_ole" do + it "creates a WIN32OLE object from OLE server name" do + shell = WIN32OLESpecs.new_ole 'Shell.Application' + shell.should be_kind_of WIN32OLE + end + + it "creates a WIN32OLE object from valid CLSID" do + shell = WIN32OLESpecs.new_ole("{13709620-C279-11CE-A49E-444553540000}") + shell.should be_kind_of WIN32OLE + end + + it "raises TypeError if argument cannot be converted to String" do + -> { WIN32OLESpecs.new_ole(42) }.should raise_error( TypeError ) + end + + it "raises WIN32OLE::RuntimeError if invalid string is given" do + -> { WIN32OLE.new('foo') }.should raise_error( WIN32OLE::RuntimeError ) + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb new file mode 100644 index 0000000000..b846685518 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#ole_func_methods" do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "raises ArgumentError if argument is given" do + -> { @dict.ole_func_methods(1) }.should raise_error ArgumentError + end + + it "returns an array of WIN32OLE::Methods" do + @dict.ole_func_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true + end + + it "contains a 'AddRef' method for Scripting Dictionary" do + @dict.ole_func_methods.map { |m| m.name }.include?('AddRef').should be_true + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb new file mode 100644 index 0000000000..b6e7f960bb --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb @@ -0,0 +1,17 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#ole_get_methods" do + + before :each do + @win32ole = WIN32OLESpecs.new_ole('Shell.Application') + end + + it "returns an array of WIN32OLE::Method objects" do + @win32ole.ole_get_methods.all? {|m| m.kind_of? WIN32OLE::Method}.should be_true + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb new file mode 100644 index 0000000000..9cb3f9e6cf --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb @@ -0,0 +1,11 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + require_relative 'shared/ole_method' + + describe "WIN32OLE#ole_method_help" do + it_behaves_like :win32ole_ole_method, :ole_method_help + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_method_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_method_spec.rb new file mode 100644 index 0000000000..e48ff8d905 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_method_spec.rb @@ -0,0 +1,11 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + require_relative 'shared/ole_method' + + describe "WIN32OLE#ole_method" do + it_behaves_like :win32ole_ole_method, :ole_method + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb new file mode 100644 index 0000000000..92c4363f78 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#ole_methods" do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "raises ArgumentError if argument is given" do + -> { @dict.ole_methods(1) }.should raise_error ArgumentError + end + + it "returns an array of WIN32OLE::Methods" do + @dict.ole_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true + end + + it "contains a 'AddRef' method for Scripting Dictionary" do + @dict.ole_methods.map { |m| m.name }.include?('AddRef').should be_true + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb new file mode 100644 index 0000000000..f298f19dba --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" + +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#ole_obj_help" do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "raises ArgumentError if argument is given" do + -> { @dict.ole_obj_help(1) }.should raise_error ArgumentError + end + + it "returns an instance of WIN32OLE::Type" do + @dict.ole_obj_help.kind_of?(WIN32OLE::Type).should be_true + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb new file mode 100644 index 0000000000..2b46ae47de --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + describe "WIN32OLE#ole_put_methods" do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "raises ArgumentError if argument is given" do + -> { @dict.ole_put_methods(1) }.should raise_error ArgumentError + end + + it "returns an array of WIN32OLE::Methods" do + @dict.ole_put_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true + end + + it "contains a 'Key' method for Scripting Dictionary" do + @dict.ole_put_methods.map { |m| m.name }.include?('Key').should be_true + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/setproperty_spec.rb b/spec/ruby/library/win32ole/win32ole/setproperty_spec.rb new file mode 100644 index 0000000000..bacdee63da --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/setproperty_spec.rb @@ -0,0 +1,11 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + require_relative 'shared/setproperty' + + describe "WIN32OLE#setproperty" do + it_behaves_like :win32ole_setproperty, :setproperty + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb b/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb new file mode 100644 index 0000000000..bae424a604 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb @@ -0,0 +1,19 @@ +platform_is :windows do + require_relative '../../fixtures/classes' + + describe :win32ole_ole_method, shared: true do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "raises ArgumentError if no argument is given" do + -> { @dict.send(@method) }.should raise_error ArgumentError + end + + it "returns the WIN32OLE::Method 'Add' if given 'Add'" do + result = @dict.send(@method, "Add") + result.kind_of?(WIN32OLE::Method).should be_true + result.name.should == 'Add' + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole/shared/setproperty.rb b/spec/ruby/library/win32ole/win32ole/shared/setproperty.rb new file mode 100644 index 0000000000..b9267aef71 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole/shared/setproperty.rb @@ -0,0 +1,23 @@ +platform_is :windows do + require_relative '../../fixtures/classes' + + describe :win32ole_setproperty, shared: true do + before :each do + @dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + end + + it "raises ArgumentError if no argument is given" do + -> { @dict.send(@method) }.should raise_error ArgumentError + end + + it "sets key to newkey and returns nil" do + oldkey = 'oldkey' + newkey = 'newkey' + @dict.add(oldkey, 'value') + result = @dict.send(@method, 'Key', oldkey, newkey) + result.should == nil + @dict[oldkey].should == nil + @dict[newkey].should == 'value' + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole_event/new_spec.rb b/spec/ruby/library/win32ole/win32ole_event/new_spec.rb new file mode 100644 index 0000000000..4efd4c3e0f --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_event/new_spec.rb @@ -0,0 +1,34 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + + guard -> { WIN32OLESpecs::MSXML_AVAILABLE } do + describe "WIN32OLE::Event.new" do + before :all do + @xml_dom = WIN32OLESpecs.new_ole('MSXML.DOMDocument') + end + + after :all do + @xml_dom = nil + end + + it "raises TypeError given invalid argument" do + -> { WIN32OLE::Event.new "A" }.should raise_error TypeError + end + + it "raises RuntimeError if event does not exist" do + -> { WIN32OLE::Event.new(@xml_dom, 'A') }.should raise_error RuntimeError + end + + it "raises RuntimeError if OLE object has no events" do + dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') + -> { WIN32OLE::Event.new(dict) }.should raise_error RuntimeError + end + + it "creates WIN32OLE::Event object" do + ev = WIN32OLE::Event.new(@xml_dom) + ev.should be_kind_of WIN32OLE::Event + end + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb b/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb new file mode 100644 index 0000000000..acc7d2d6b6 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb @@ -0,0 +1,71 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + guard -> { WIN32OLESpecs::MSXML_AVAILABLE } do + + def handler_global(event, *args) + @event_global += event + end + + def handler_specific(*args) + @event_specific = "specific" + end + + def handler_spec_alt(*args) + @event_spec_alt = "spec_alt" + end + + describe "WIN32OLE::Event#on_event" do + before :all do + @fn_xml = File.absolute_path "../fixtures/event.xml", __dir__ + end + + before :each do + @xml_dom = WIN32OLESpecs.new_ole 'MSXML.DOMDocument' + @xml_dom.async = true + @ev = WIN32OLE::Event.new @xml_dom + @event_global = '' + @event_specific = '' + @event_spec_alt = '' + end + + after :each do + @xml_dom = nil + @ev = nil + end + + it "sets global event handler properly, and the handler is invoked by event loop" do + @ev.on_event { |*args| handler_global(*args) } + @xml_dom.loadXML "<program><name>Ruby</name><version>trunk</version></program>" + WIN32OLE::Event.message_loop + @event_global.should =~ /onreadystatechange/ + end + + it "accepts a String argument and the handler is invoked by event loop" do + @ev.on_event("onreadystatechange") { |*args| @event = 'foo' } + @xml_dom.loadXML "<program><name>Ruby</name><version>trunk</version></program>" + WIN32OLE::Event.message_loop + @event.should =~ /foo/ + end + + it "accepts a Symbol argument and the handler is invoked by event loop" do + @ev.on_event(:onreadystatechange) { |*args| @event = 'bar' } + @xml_dom.loadXML "<program><name>Ruby</name><version>trunk</version></program>" + WIN32OLE::Event.message_loop + @event.should =~ /bar/ + end + + it "accepts a specific event handler and overrides a global event handler" do + @ev.on_event { |*args| handler_global(*args) } + @ev.on_event("onreadystatechange") { |*args| handler_specific(*args) } + @ev.on_event("onreadystatechange") { |*args| handler_spec_alt(*args) } + @xml_dom.load @fn_xml + WIN32OLE::Event.message_loop + @event_global.should == 'ondataavailable' + @event_global.should_not =~ /onreadystatechange/ + @event_specific.should == '' + @event_spec_alt.should == "spec_alt" + end + end + end +end diff --git a/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb b/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb new file mode 100644 index 0000000000..e5f55f2d38 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#dispid" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m = WIN32OLE::Method.new(ole_type, "namespace") + end + + it "raises ArgumentError if argument is given" do + -> { @m.dispid(0) }.should raise_error ArgumentError + end + + it "returns expected dispatch ID for Shell's 'namespace' method" do + @m.dispid.should == 1610743810 # value found in MRI's test + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb b/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb new file mode 100644 index 0000000000..bea47348ee --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb @@ -0,0 +1,29 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + guard -> { WIN32OLESpecs::SYSTEM_MONITOR_CONTROL_AVAILABLE } do + + describe "WIN32OLE::Method#event_interface" do + before :each do + ole_type = WIN32OLE::Type.new("System Monitor Control", "SystemMonitor") + @on_dbl_click_method = WIN32OLE::Method.new(ole_type, "OnDblClick") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @namespace_method = WIN32OLE::Method.new(ole_type, "namespace") + end + + it "raises ArgumentError if argument is given" do + -> { @on_dbl_click_method.event_interface(1) }.should raise_error ArgumentError + end + + it "returns expected string for System Monitor Control's 'OnDblClick' method" do + @on_dbl_click_method.event_interface.should == "DISystemMonitorEvents" + end + + it "returns nil if method has no event interface" do + @namespace_method.event_interface.should be_nil + end + + end + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/event_spec.rb b/spec/ruby/library/win32ole/win32ole_method/event_spec.rb new file mode 100644 index 0000000000..5a94cf5ce6 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/event_spec.rb @@ -0,0 +1,23 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require_relative '../fixtures/classes' + guard -> { WIN32OLESpecs::SYSTEM_MONITOR_CONTROL_AVAILABLE } do + + describe "WIN32OLE::Method#event?" do + before :each do + ole_type = WIN32OLE::Type.new("System Monitor Control", "SystemMonitor") + @on_dbl_click_method = WIN32OLE::Method.new(ole_type, "OnDblClick") + end + + it "raises ArgumentError if argument is given" do + -> { @on_dbl_click_method.event?(1) }.should raise_error ArgumentError + end + + it "returns true for System Monitor Control's 'OnDblClick' method" do + @on_dbl_click_method.event?.should be_true + end + + end + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb new file mode 100644 index 0000000000..83f34b9c10 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb @@ -0,0 +1,27 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#helpcontext" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + @get_file_version = WIN32OLE::Method.new(ole_type, "GetFileVersion") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @get_file_version.helpcontext(1) }.should raise_error ArgumentError + end + + it "returns expected value for FileSystemObject's 'GetFileVersion' method" do + @get_file_version.helpcontext.should == 0 + end + + it "returns expected value for Scripting Runtime's 'name' method" do + @m_file_name.helpcontext.should == 2181996 # value indicated in MRI's test + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb new file mode 100644 index 0000000000..9cf9d63d3b --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#helpfile" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.helpfile(1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'File' method" do + @m_file_name.helpfile.should =~ /VBENLR.*\.CHM$/i + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb new file mode 100644 index 0000000000..5ae4a5e090 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#helpstring" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.helpstring(1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'File' method" do + @m_file_name.helpstring.should == "Get name of file" # value indicated in MRI's test + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb b/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb new file mode 100644 index 0000000000..06acbb58a5 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#invkind" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.invkind(1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'name' method" do + @m_file_name.invkind.should == 2 + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb b/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb new file mode 100644 index 0000000000..0e97ec3305 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#invoke_kind" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.invoke_kind(1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'name' method" do + @m_file_name.invoke_kind.should =~ /^(UNKNOWN|PROPERTY|PROPERTYGET|PROPERTYPUT|PROPERTYPUTREF|FUNC)$/ + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/name_spec.rb b/spec/ruby/library/win32ole/win32ole_method/name_spec.rb new file mode 100644 index 0000000000..6e2e233a62 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/name_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#name" do + it_behaves_like :win32ole_method_name, :name + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/new_spec.rb b/spec/ruby/library/win32ole/win32ole_method/new_spec.rb new file mode 100644 index 0000000000..46186ae566 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/new_spec.rb @@ -0,0 +1,34 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method.new" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + it "raises TypeError when given non-strings" do + -> { WIN32OLE::Method.new(1, 2) }.should raise_error TypeError + end + + it "raises ArgumentError if only 1 argument is given" do + -> { WIN32OLE::Method.new("hello") }.should raise_error ArgumentError + -> { WIN32OLE::Method.new(@ole_type) }.should raise_error ArgumentError + end + + it "returns a valid WIN32OLE::Method object" do + WIN32OLE::Method.new(@ole_type, "Open").should be_kind_of WIN32OLE::Method + WIN32OLE::Method.new(@ole_type, "open").should be_kind_of WIN32OLE::Method + end + + it "raises WIN32OLE::RuntimeError if the method does not exist" do + -> { WIN32OLE::Method.new(@ole_type, "NonexistentMethod") }.should raise_error WIN32OLE::RuntimeError + end + + it "raises TypeError if second argument is not a String" do + -> { WIN32OLE::Method.new(@ole_type, 5) }.should raise_error TypeError + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb b/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb new file mode 100644 index 0000000000..3c80cb3c2a --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#offset_vtbl" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.offset_vtbl(1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'name' method" do + pointer_size = PlatformGuard::POINTER_SIZE + @m_file_name.offset_vtbl.should == pointer_size + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/params_spec.rb new file mode 100644 index 0000000000..0b1b4595a3 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/params_spec.rb @@ -0,0 +1,29 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#params" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.params(1) }.should raise_error ArgumentError + end + + it "returns empty array for Scripting Runtime's 'name' method" do + @m_file_name.params.should be_kind_of Array + @m_file_name.params.should be_empty + end + + it "returns 4-element array of WIN32OLE::Param for Shell's 'BrowseForFolder' method" do + @m_browse_for_folder.params.all? { |p| p.kind_of? WIN32OLE::Param }.should be_true + @m_browse_for_folder.params.size == 4 + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb new file mode 100644 index 0000000000..c3725bfef2 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#return_type_detail" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + end + + it "raises ArgumentError if argument is given" do + -> { @m_browse_for_folder.return_type_detail(1) }.should raise_error ArgumentError + end + + it "returns expected value for Shell Control's 'BrowseForFolder' method" do + @m_browse_for_folder.return_type_detail.should be_kind_of Array + @m_browse_for_folder.return_type_detail.should == ['PTR', 'USERDEFINED', 'Folder'] + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb new file mode 100644 index 0000000000..9e5a1eb1df --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#return_type" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.return_type(1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'name' method" do + @m_file_name.return_type.should == 'BSTR' + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb new file mode 100644 index 0000000000..34fd135b8c --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#return_vtype" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + end + + it "raises ArgumentError if argument is given" do + -> { @m_browse_for_folder.return_vtype(1) }.should raise_error ArgumentError + end + + it "returns expected value for Shell Control's 'BrowseForFolder' method" do + @m_browse_for_folder.return_vtype.should == 26 + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/shared/name.rb b/spec/ruby/library/win32ole/win32ole_method/shared/name.rb new file mode 100644 index 0000000000..7e2197ca5a --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/shared/name.rb @@ -0,0 +1,20 @@ +platform_is :windows do + require 'win32ole' + + describe :win32ole_method_name, shared: true do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + end + + it "raises ArgumentError if argument is given" do + -> { @m_file_name.send(@method, 1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'name' method" do + @m_file_name.send(@method).should == 'Name' # note the capitalization + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb new file mode 100644 index 0000000000..38cb21ccef --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#size_opt_params" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + end + + it "raises ArgumentError if argument is given" do + -> { @m_browse_for_folder.size_opt_params(1) }.should raise_error ArgumentError + end + + it "returns expected value for Shell Control's 'BrowseForFolder' method" do + @m_browse_for_folder.size_opt_params.should == 1 + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb new file mode 100644 index 0000000000..5d0a35a0ef --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#size_params" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + end + + it "raises ArgumentError if argument is given" do + -> { @m_browse_for_folder.size_params(1) }.should raise_error ArgumentError + end + + it "returns expected value for Shell Control's 'BrowseForFolder' method" do + @m_browse_for_folder.size_params.should == 4 + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb new file mode 100644 index 0000000000..cdcc4525b1 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#name" do + it_behaves_like :win32ole_method_name, :to_s + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb new file mode 100644 index 0000000000..2f02c15c8b --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb @@ -0,0 +1,21 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Method#visible?" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + end + + it "raises ArgumentError if argument is given" do + -> { @m_browse_for_folder.visible?(1) }.should raise_error ArgumentError + end + + it "returns true for Shell Control's 'BrowseForFolder' method" do + @m_browse_for_folder.visible?.should be_true + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/default_spec.rb b/spec/ruby/library/win32ole/win32ole_param/default_spec.rb new file mode 100644 index 0000000000..a37b03866d --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/default_spec.rb @@ -0,0 +1,32 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#default" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") + @params = m_browse_for_folder.params + + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @params[0].default(1) }.should raise_error ArgumentError + end + + it "returns nil for each of WIN32OLE::Param for Shell's 'BrowseForFolder' method" do + @params.each do |p| + p.default.should be_nil + end + end + + it "returns true for 3rd parameter of FileSystemObject's 'CopyFile' method" do + @param_overwritefiles.default.should == true # not be_true + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/input_spec.rb b/spec/ruby/library/win32ole/win32ole_param/input_spec.rb new file mode 100644 index 0000000000..d7e27d7739 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/input_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#input?" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @param_overwritefiles.input?(1) }.should raise_error ArgumentError + end + + it "returns true for 3rd parameter of FileSystemObject's 'CopyFile' method" do + @param_overwritefiles.should.input? + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/name_spec.rb b/spec/ruby/library/win32ole/win32ole_param/name_spec.rb new file mode 100644 index 0000000000..2c3474ffb3 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/name_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#name" do + it_behaves_like :win32ole_param_name, :name + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb new file mode 100644 index 0000000000..e3379dbf3e --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#ole_type_detail" do + before :each do + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @param_overwritefiles.ole_type_detail(1) }.should raise_error ArgumentError + end + + it "returns ['BOOL'] for 3rd parameter of FileSystemObject's 'CopyFile' method" do + @param_overwritefiles.ole_type_detail.should == ['BOOL'] + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb new file mode 100644 index 0000000000..a7b6666807 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#ole_type" do + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @param_overwritefiles.ole_type(1) }.should raise_error ArgumentError + end + + it "returns 'BOOL' for 3rd parameter of FileSystemObject's 'CopyFile' method" do + @param_overwritefiles.ole_type.should == 'BOOL' + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb b/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb new file mode 100644 index 0000000000..50e95fc77f --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#optional?" do + before :each do + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @param_overwritefiles.optional?(1) }.should raise_error ArgumentError + end + + it "returns true for 3rd parameter of FileSystemObject's 'CopyFile' method" do + @param_overwritefiles.optional?.should be_true + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb b/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb new file mode 100644 index 0000000000..fa4a09ea0c --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb @@ -0,0 +1,22 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#retval?" do + before :each do + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @param_overwritefiles.retval?(1) }.should raise_error ArgumentError + end + + it "returns false for 3rd parameter of FileSystemObject's 'CopyFile' method" do + @param_overwritefiles.retval?.should be_false + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/shared/name.rb b/spec/ruby/library/win32ole/win32ole_param/shared/name.rb new file mode 100644 index 0000000000..56ff24ddc8 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/shared/name.rb @@ -0,0 +1,21 @@ +platform_is :windows do + require 'win32ole' + + describe :win32ole_param_name, shared: true do + before :each do + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") + @param_overwritefiles = m_copyfile.params[2] + end + + it "raises ArgumentError if argument is given" do + -> { @param_overwritefiles.send(@method, 1) }.should raise_error ArgumentError + end + + it "returns expected value for Scripting Runtime's 'name' method" do + @param_overwritefiles.send(@method).should == 'OverWriteFiles' # note the capitalization + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb new file mode 100644 index 0000000000..c59f426692 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Param#to_s" do + it_behaves_like :win32ole_param_name, :to_s + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb b/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb new file mode 100644 index 0000000000..e574a945ad --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#guid for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns String with expected format" do + @ole_type.guid.should =~ /\A\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}\z/ + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb new file mode 100644 index 0000000000..35911fec52 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#helpcontext for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an Integer" do + @ole_type.helpcontext.should be_kind_of Integer + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb new file mode 100644 index 0000000000..7bd61a1c40 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#helpfile for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an empty string" do + @ole_type.helpfile.should be_empty + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb new file mode 100644 index 0000000000..940475b25e --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#helpstring for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns expected string" do + @ole_type.helpstring.should == "Shell Object Type Information" + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb b/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb new file mode 100644 index 0000000000..598e5bcef8 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#major_version for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an Integer" do + @ole_type.major_version.should be_kind_of Integer + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb b/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb new file mode 100644 index 0000000000..59cfb94012 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#minor_version for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an Integer" do + @ole_type.minor_version.should be_kind_of Integer + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/name_spec.rb b/spec/ruby/library/win32ole/win32ole_type/name_spec.rb new file mode 100644 index 0000000000..4cc3426872 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/name_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#name" do + it_behaves_like :win32ole_type_name, :name + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_type/new_spec.rb b/spec/ruby/library/win32ole/win32ole_type/new_spec.rb new file mode 100644 index 0000000000..185a235940 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/new_spec.rb @@ -0,0 +1,41 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type.new" do + it "raises ArgumentError with no argument" do + -> { WIN32OLE::Type.new }.should raise_error ArgumentError + end + + it "raises ArgumentError with invalid string" do + -> { WIN32OLE::Type.new("foo") }.should raise_error ArgumentError + end + + it "raises TypeError if second argument is not a String" do + -> { WIN32OLE::Type.new(1,2) }.should raise_error TypeError + -> { + WIN32OLE::Type.new('Microsoft Shell Controls And Automation',2) + }.should raise_error TypeError + end + + it "raise WIN32OLE::RuntimeError if OLE object specified is not found" do + -> { + WIN32OLE::Type.new('Microsoft Shell Controls And Automation','foo') + }.should raise_error WIN32OLE::RuntimeError + -> { + WIN32OLE::Type.new('Microsoft Shell Controls And Automation','Application') + }.should raise_error WIN32OLE::RuntimeError + end + + it "creates WIN32OLE::Type object from name and valid type" do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + ole_type.should be_kind_of WIN32OLE::Type + end + + it "creates WIN32OLE::Type object from CLSID and valid type" do + ole_type2 = WIN32OLE::Type.new("{13709620-C279-11CE-A49E-444553540000}", "Shell") + ole_type2.should be_kind_of WIN32OLE::Type + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb new file mode 100644 index 0000000000..ed14e37a95 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type.ole_classes for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns array of WIN32OLE_TYPEs" do + WIN32OLE::Type.ole_classes("Microsoft Shell Controls And Automation").all? {|e| e.kind_of? WIN32OLE::Type }.should be_true + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb new file mode 100644 index 0000000000..0c031abaa6 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#ole_methods for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an Integer" do + @ole_type.ole_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb new file mode 100644 index 0000000000..49c1902f8c --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#ole_type for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns string 'Class'" do + @ole_type.ole_type.should == "Class" + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb b/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb new file mode 100644 index 0000000000..9a700426d9 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#progid for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns expected string" do + @ole_type.progid.should == "Shell.Application.1" + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb b/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb new file mode 100644 index 0000000000..b1b57960cd --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb @@ -0,0 +1,15 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type.progids" do + it "raises ArgumentError if an argument is given" do + -> { WIN32OLE::Type.progids(1) }.should raise_error ArgumentError + end + + it "returns an array containing 'Shell.Explorer'" do + WIN32OLE::Type.progids().include?('Shell.Explorer').should be_true + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/shared/name.rb b/spec/ruby/library/win32ole/win32ole_type/shared/name.rb new file mode 100644 index 0000000000..efae7aeec1 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/shared/name.rb @@ -0,0 +1,19 @@ +platform_is :windows do + require 'win32ole' + + describe :win32ole_type_name, shared: true do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + end + + it "raises ArgumentError if argument is given" do + -> { @ole_type.send(@method, 1) }.should raise_error ArgumentError + end + + it "returns a String" do + @ole_type.send(@method).should == 'ShellSpecialFolderConstants' + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb b/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb new file mode 100644 index 0000000000..3c7651cc1f --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#src_type for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns nil" do + @ole_type.src_type.should be_nil + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb new file mode 100644 index 0000000000..03a0344fdb --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#to_s" do + it_behaves_like :win32ole_type_name, :to_s + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb b/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb new file mode 100644 index 0000000000..8b62f3e2eb --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#typekind for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an Integer" do + @ole_type.typekind.should be_kind_of Integer + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb b/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb new file mode 100644 index 0000000000..71d7cf00f7 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb @@ -0,0 +1,23 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type.typelibs for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "raises ArgumentError if any argument is give" do + -> { WIN32OLE::Type.typelibs(1) }.should raise_error ArgumentError + end + + it "returns array of type libraries" do + WIN32OLE::Type.typelibs().include?("Microsoft Shell Controls And Automation").should be_true + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb b/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb new file mode 100644 index 0000000000..b1a407523c --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#variables for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns an empty array" do + @ole_type.variables.should == [] + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb new file mode 100644 index 0000000000..05c54c8838 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE::Type#visible? for Shell Controls" do + before :each do + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + end + + after :each do + @ole_type = nil + end + + it "returns true" do + @ole_type.visible?.should be_true + end + + end +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/name_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/name_spec.rb new file mode 100644 index 0000000000..dd9bfa594f --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/name_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#name" do + it_behaves_like :win32ole_variable_new, :name + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb new file mode 100644 index 0000000000..89576ceedc --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb @@ -0,0 +1,20 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#ole_type_detail" do + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns a nonempty Array" do + @var.ole_type_detail.should be_kind_of Array + @var.ole_type_detail.should_not be_empty + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb new file mode 100644 index 0000000000..441011f1e7 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#ole_type" do + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns a String" do + @var.ole_type.should be_kind_of String + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb b/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb new file mode 100644 index 0000000000..d02942ce0a --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb @@ -0,0 +1,18 @@ +platform_is :windows do + require 'win32ole' + + describe :win32ole_variable_new, shared: true do + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns a String" do + @var.send(@method).should be_kind_of String + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb new file mode 100644 index 0000000000..d4cab8e924 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb @@ -0,0 +1,12 @@ +require_relative "../../../spec_helper" +require_relative 'shared/name' + +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#to_s" do + it_behaves_like :win32ole_variable_new, :to_s + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb new file mode 100644 index 0000000000..d26273ebed --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb @@ -0,0 +1,20 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#value" do + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns an Integer" do + # according to doc, this could return nil + @var.value.should be_kind_of Integer + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb new file mode 100644 index 0000000000..17bc47160a --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb @@ -0,0 +1,20 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#variable_kind" do + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns a String" do + @var.variable_kind.should be_kind_of String + @var.variable_kind.should == 'CONSTANT' + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb new file mode 100644 index 0000000000..c5f8164509 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb @@ -0,0 +1,20 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#varkind" do + # TODO review + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns an Integer" do + @var.varkind.should be_kind_of Integer + end + + end + +end diff --git a/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb new file mode 100644 index 0000000000..ba53a81de0 --- /dev/null +++ b/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb @@ -0,0 +1,19 @@ +require_relative "../../../spec_helper" +platform_is :windows do + require 'win32ole' + + describe "WIN32OLE_VARIABLE#visible?" do + # not sure how WIN32OLE_VARIABLE objects are supposed to be generated + # WIN32OLE_VARIABLE.new even seg faults in some cases + before :each do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @var = ole_type.variables[0] + end + + it "returns a String" do + @var.visible?.should be_true + end + + end + +end diff --git a/spec/ruby/library/yaml/dump_spec.rb b/spec/ruby/library/yaml/dump_spec.rb new file mode 100644 index 0000000000..97b665d6a5 --- /dev/null +++ b/spec/ruby/library/yaml/dump_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' + +require 'yaml' + +describe "YAML.dump" do + before :each do + @test_file = tmp("yaml_test_file") + end + + after :each do + rm_r @test_file + end + + it "converts an object to YAML and write result to io when io provided" do + File.open(@test_file, 'w' ) do |io| + YAML.dump( ['badger', 'elephant', 'tiger'], io ) + end + YAML.load_file(@test_file).should == ['badger', 'elephant', 'tiger'] + end + + it "returns a string containing dumped YAML when no io provided" do + YAML.dump( :locked ).should match_yaml("--- :locked\n") + end + + it "returns the same string that #to_yaml on objects" do + ["a", "b", "c"].to_yaml.should == YAML.dump(["a", "b", "c"]) + end + + it "dumps strings into YAML strings" do + YAML.dump("str").should match_yaml("--- str\n") + end + + it "dumps hashes into YAML key-values" do + YAML.dump({ "a" => "b" }).should match_yaml("--- \na: b\n") + end + + it "dumps Arrays into YAML collection" do + YAML.dump(["a", "b", "c"]).should match_yaml("--- \n- a\n- b\n- c\n") + end + + it "dumps an OpenStruct" do + begin + require "ostruct" + rescue LoadError + skip "OpenStruct is not available" + end + os = OpenStruct.new("age" => 20, "name" => "John") + yaml_dump = YAML.dump(os) + + [ + "--- !ruby/object:OpenStruct\nage: 20\nname: John\n", + "--- !ruby/object:OpenStruct\ntable:\n :age: 20\n :name: John\n", + ].should.include?(yaml_dump) + end + + it "dumps a File without any state" do + file = File.new(__FILE__) + begin + YAML.dump(file).should match_yaml("--- !ruby/object:File {}\n") + ensure + file.close + end + end +end diff --git a/spec/ruby/library/yaml/dump_stream_spec.rb b/spec/ruby/library/yaml/dump_stream_spec.rb new file mode 100644 index 0000000000..f0578fa800 --- /dev/null +++ b/spec/ruby/library/yaml/dump_stream_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +require 'yaml' + +describe "YAML.dump_stream" do + it "returns a YAML stream containing the objects passed" do + YAML.dump_stream('foo', 20, [], {}).should match_yaml("--- foo\n--- 20\n--- []\n\n--- {}\n\n") + end +end diff --git a/spec/ruby/library/yaml/fixtures/example_class.rb b/spec/ruby/library/yaml/fixtures/example_class.rb new file mode 100644 index 0000000000..8259870799 --- /dev/null +++ b/spec/ruby/library/yaml/fixtures/example_class.rb @@ -0,0 +1,7 @@ +module YAMLSpecs + class Example + def initialize(name) + @name = name + end + end +end diff --git a/spec/ruby/library/yaml/fixtures/strings.rb b/spec/ruby/library/yaml/fixtures/strings.rb new file mode 100644 index 0000000000..f478f89823 --- /dev/null +++ b/spec/ruby/library/yaml/fixtures/strings.rb @@ -0,0 +1,26 @@ +module YAMLSpecs + COMPLEX_KEY_1 = <<~EOY + ? # PLAY SCHEDULE + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 + + ? [ New York Yankees, + Atlanta Braves ] + : [ 2001-07-02, 2001-08-12, + 2001-08-14 ] + EOY + + MULTIDOCUMENT = <<~EOY + --- + - Mark McGwire + - Sammy Sosa + - Ken Griffey + + # Team ranking + --- + - Chicago Cubs + - St Louis Cardinals + EOY +end diff --git a/spec/ruby/library/yaml/fixtures/test_yaml.yml b/spec/ruby/library/yaml/fixtures/test_yaml.yml new file mode 100644 index 0000000000..efe3b5cc1a --- /dev/null +++ b/spec/ruby/library/yaml/fixtures/test_yaml.yml @@ -0,0 +1,2 @@ +project: + name: RubySpec diff --git a/spec/ruby/library/yaml/load_file_spec.rb b/spec/ruby/library/yaml/load_file_spec.rb new file mode 100644 index 0000000000..4941d0485b --- /dev/null +++ b/spec/ruby/library/yaml/load_file_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' + +require 'yaml' + +describe "YAML.load_file" do + before :each do + @test_file = tmp("yaml_test_file") + end + + after :each do + rm_r @test_file + end + + it "returns a hash" do + File.open(@test_file,'w' ){|io| YAML.dump( {"bar"=>2, "car"=>1}, io ) } + YAML.load_file(@test_file).should == {"bar"=>2, "car"=>1} + end +end diff --git a/spec/ruby/library/yaml/load_spec.rb b/spec/ruby/library/yaml/load_spec.rb new file mode 100644 index 0000000000..56700a85f9 --- /dev/null +++ b/spec/ruby/library/yaml/load_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative 'shared/load' + +describe "YAML.load" do + it_behaves_like :yaml_load_safe, :load + + guard -> { Psych::VERSION < "4.0.0" } do + it_behaves_like :yaml_load_unsafe, :load + end +end diff --git a/spec/ruby/library/yaml/load_stream_spec.rb b/spec/ruby/library/yaml/load_stream_spec.rb new file mode 100644 index 0000000000..31bc862f5e --- /dev/null +++ b/spec/ruby/library/yaml/load_stream_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/strings' +require_relative 'shared/each_document' + +require 'yaml' + +describe "YAML.load_stream" do + it_behaves_like :yaml_each_document, :load_stream +end diff --git a/spec/ruby/library/yaml/parse_file_spec.rb b/spec/ruby/library/yaml/parse_file_spec.rb new file mode 100644 index 0000000000..7bffcdc62f --- /dev/null +++ b/spec/ruby/library/yaml/parse_file_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +require 'yaml' + +describe "YAML.parse_file" do + it "returns a YAML::Syck::Map object after parsing a YAML file" do + test_parse_file = fixture __FILE__, "test_yaml.yml" + YAML.parse_file(test_parse_file).should be_kind_of(Psych::Nodes::Document) + end +end diff --git a/spec/ruby/library/yaml/parse_spec.rb b/spec/ruby/library/yaml/parse_spec.rb new file mode 100644 index 0000000000..37e2b7fa0a --- /dev/null +++ b/spec/ruby/library/yaml/parse_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' + +require 'yaml' + +describe "YAML.parse with an empty string" do + it "returns false" do + YAML.parse('').should be_false + end +end + +describe "YAML.parse" do + before :each do + @string_yaml = "foo".to_yaml + end + + it "returns the value from the object" do + if YAML.to_s == "Psych" + YAML.parse(@string_yaml).to_ruby.should == "foo" + else + YAML.parse(@string_yaml).value.should == "foo" + end + end +end diff --git a/spec/ruby/library/yaml/shared/each_document.rb b/spec/ruby/library/yaml/shared/each_document.rb new file mode 100644 index 0000000000..6f00aee297 --- /dev/null +++ b/spec/ruby/library/yaml/shared/each_document.rb @@ -0,0 +1,19 @@ +describe :yaml_each_document, shared: true do + it "calls the block on each successive document" do + documents = [] + YAML.send(@method, YAMLSpecs::MULTIDOCUMENT) do |doc| + documents << doc + end + documents.should == [["Mark McGwire", "Sammy Sosa", "Ken Griffey"], + ["Chicago Cubs", "St Louis Cardinals"]] + end + + it "works on files" do + test_parse_file = fixture __FILE__, "test_yaml.yml" + File.open(test_parse_file, "r") do |file| + YAML.send(@method, file) do |doc| + doc.should == {"project"=>{"name"=>"RubySpec"}} + end + end + end +end diff --git a/spec/ruby/library/yaml/shared/load.rb b/spec/ruby/library/yaml/shared/load.rb new file mode 100644 index 0000000000..b8bb605b0a --- /dev/null +++ b/spec/ruby/library/yaml/shared/load.rb @@ -0,0 +1,142 @@ +require_relative '../fixtures/strings' + +require 'yaml' + +describe :yaml_load_safe, shared: true do + it "returns a document from current io stream when io provided" do + @test_file = tmp("yaml_test_file") + File.open(@test_file, 'w') do |io| + YAML.dump( ['badger', 'elephant', 'tiger'], io ) + end + File.open(@test_file) { |yf| YAML.send(@method, yf ) }.should == ['badger', 'elephant', 'tiger'] + ensure + rm_r @test_file + end + + it "loads strings" do + strings = ["str", + " str", + "'str'", + "str", + " str", + "'str'", + "\"str\"", + "\n str", + "--- str", + "---\nstr", + "--- \nstr", + "--- \n str", + "--- 'str'" + ] + strings.each do |str| + YAML.send(@method, str).should == "str" + end + end + + it "loads strings with chars from non-base Unicode plane" do + # We add these strings as bytes and force the encoding for safety + # as bugs in parsing unicode characters can obscure bugs in this + # area. + + yaml_and_strings = { + # "--- 🌵" => "🌵" + [45, 45, 45, 32, 240, 159, 140, 181] => + [240, 159, 140, 181], + # "--- 🌵 and some text" => "🌵 and some text" + [45, 45, 45, 32, 240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116] => + [240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116], + # "--- Some text 🌵 and some text" => "Some text 🌵 and some text" + [45, 45, 45, 32, 83, 111, 109, 101, 32, 116, 101, 120, 116, 32, 240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116] => + [83, 111, 109, 101, 32, 116, 101, 120, 116, 32, 240, 159, 140, 181, 32, 97, 110, 100, 32, 115, 111, 109, 101, 32, 116, 101, 120, 116] + } + yaml_and_strings.each do |yaml, str| + YAML.send(@method, yaml.pack("C*").force_encoding("UTF-8")).should == str.pack("C*").force_encoding("UTF-8") + end + end + + it "fails on invalid keys" do + if YAML.to_s == "Psych" + error = Psych::SyntaxError + else + error = ArgumentError + end + -> { YAML.send(@method, "key1: value\ninvalid_key") }.should raise_error(error) + end + + it "accepts symbols" do + YAML.send(@method, "--- :locked" ).should == :locked + end + + it "accepts numbers" do + YAML.send(@method, "47").should == 47 + YAML.send(@method, "-1").should == -1 + end + + it "accepts collections" do + expected = ["a", "b", "c"] + YAML.send(@method, "--- \n- a\n- b\n- c\n").should == expected + YAML.send(@method, "--- [a, b, c]").should == expected + YAML.send(@method, "[a, b, c]").should == expected + end + + it "parses start markers" do + YAML.send(@method, "---\n").should == nil + YAML.send(@method, "--- ---\n").should == "---" + YAML.send(@method, "--- abc").should == "abc" + end + + it "works with block sequence shortcuts" do + block_seq = "- - - one\n - two\n - three" + YAML.send(@method, block_seq).should == [[["one", "two", "three"]]] + end + + it "loads a symbol key that contains spaces" do + string = ":user name: This is the user name." + expected = { :"user name" => "This is the user name."} + YAML.send(@method, string).should == expected + end +end + +describe :yaml_load_unsafe, shared: true do + it "works on complex keys" do + require 'date' + expected = { + [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ], + [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), + Date.new( 2001, 8, 12 ), + Date.new( 2001, 8, 14 ) ] + } + YAML.send(@method, YAMLSpecs::COMPLEX_KEY_1).should == expected + end + + describe "with iso8601 timestamp" do + it "computes the microseconds" do + [ [YAML.send(@method, "2011-03-22t23:32:11.2233+01:00"), 223300], + [YAML.send(@method, "2011-03-22t23:32:11.0099+01:00"), 9900], + [YAML.send(@method, "2011-03-22t23:32:11.000076+01:00"), 76] + ].should be_computed_by(:usec) + end + + it "rounds values smaller than 1 usec to 0 " do + YAML.send(@method, "2011-03-22t23:32:11.000000342222+01:00").usec.should == 0 + end + end + + it "loads an OpenStruct" do + begin + require "ostruct" + rescue LoadError + skip "OpenStruct is not available" + end + os = OpenStruct.new("age" => 20, "name" => "John") + loaded = YAML.send(@method, "--- !ruby/object:OpenStruct\ntable:\n :age: 20\n :name: John\n") + loaded.should == os + end + + it "loads a File but raise an error when used as it is uninitialized" do + loaded = YAML.send(@method, "--- !ruby/object:File {}\n") + -> { + loaded.read(1) + }.should raise_error(IOError) + end +end diff --git a/spec/ruby/library/yaml/to_yaml_spec.rb b/spec/ruby/library/yaml/to_yaml_spec.rb new file mode 100644 index 0000000000..08c5451416 --- /dev/null +++ b/spec/ruby/library/yaml/to_yaml_spec.rb @@ -0,0 +1,114 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/example_class' + +require 'yaml' + +describe "Object#to_yaml" do + + it "returns the YAML representation of an Array object" do + %w( 30 ruby maz irb 99 ).to_yaml.gsub("'", '"').should match_yaml("--- \n- \"30\"\n- ruby\n- maz\n- irb\n- \"99\"\n") + end + + it "returns the YAML representation of a Hash object" do + { "a" => "b"}.to_yaml.should match_yaml("--- \na: b\n") + end + + it "returns the YAML representation of an object" do + YAMLSpecs::Example.new("baz").to_yaml.should match_yaml("--- !ruby/object:YAMLSpecs::Example\nname: baz\n") + end + + it "returns the YAML representation of a Class object" do + YAMLSpecs::Example.to_yaml.should match_yaml("--- !ruby/class 'YAMLSpecs::Example'\n") + end + + it "returns the YAML representation of a Module object" do + Enumerable.to_yaml.should match_yaml("--- !ruby/module 'Enumerable'\n") + end + + it "returns the YAML representation of a Date object" do + require 'date' + Date.new(1997, 12, 30).to_yaml.should match_yaml("--- 1997-12-30\n") + end + + it "returns the YAML representation of a FalseClass" do + false_klass = false + false_klass.should be_kind_of(FalseClass) + false_klass.to_yaml.should match_yaml("--- false\n") + end + + it "returns the YAML representation of a Float object" do + float = 1.2 + float.should be_kind_of(Float) + float.to_yaml.should match_yaml("--- 1.2\n") + end + + it "returns the YAML representation of an Integer object" do + int = 20 + int.should be_kind_of(Integer) + int.to_yaml.should match_yaml("--- 20\n") + end + + it "returns the YAML representation of a NilClass object" do + nil_klass = nil + nil_klass.should be_kind_of(NilClass) + nil_klass.to_yaml.should match_yaml("--- \n") + end + + it "returns the YAML representation of a RegExp object" do + Regexp.new('^a-z+:\\s+\w+').to_yaml.should match_yaml("--- !ruby/regexp /^a-z+:\\s+\\w+/\n") + end + + it "returns the YAML representation of a String object" do + "I love Ruby".to_yaml.should match_yaml("--- I love Ruby\n") + end + + it "returns the YAML representation of a Struct object" do + Person = Struct.new(:name, :gender) + Person.new("Jane", "female").to_yaml.should match_yaml("--- !ruby/struct:Person\nname: Jane\ngender: female\n") + ensure + Object.send(:remove_const, :Person) + end + + it "returns the YAML representation of an unnamed Struct object" do + person = Struct.new(:name, :gender) + person.new("Jane", "female").to_yaml.should match_yaml("--- !ruby/struct\nname: Jane\ngender: female\n") + end + + it "returns the YAML representation of a Symbol object" do + :symbol.to_yaml.should match_yaml("--- :symbol\n") + end + + it "returns the YAML representation of a Time object" do + Time.utc(2000,"jan",1,20,15,1).to_yaml.sub(/\.0+/, "").should match_yaml("--- 2000-01-01 20:15:01 Z\n") + end + + it "returns the YAML representation of a TrueClass" do + true_klass = true + true_klass.should be_kind_of(TrueClass) + true_klass.to_yaml.should match_yaml("--- true\n") + end + + it "returns the YAML representation of a Error object" do + StandardError.new("foobar").to_yaml.should match_yaml("--- !ruby/exception:StandardError\nmessage: foobar\nbacktrace: \n") + end + + it "returns the YAML representation for Range objects" do + yaml = Range.new(1,3).to_yaml + yaml.include?("!ruby/range").should be_true + yaml.include?("begin: 1").should be_true + yaml.include?("end: 3").should be_true + yaml.include?("excl: false").should be_true + end + + it "returns the YAML representation of numeric constants" do + nan_value.to_yaml.downcase.should match_yaml("--- .nan\n") + infinity_value.to_yaml.downcase.should match_yaml("--- .inf\n") + (-infinity_value).to_yaml.downcase.should match_yaml("--- -.inf\n") + (0.0).to_yaml.should match_yaml("--- 0.0\n") + end + + it "returns the YAML representation of an array of hashes" do + players = [{"a" => "b"}, {"b" => "c"}] + players.to_yaml.should match_yaml("--- \n- a: b\n- b: c\n") + end +end diff --git a/spec/ruby/library/yaml/unsafe_load_spec.rb b/spec/ruby/library/yaml/unsafe_load_spec.rb new file mode 100644 index 0000000000..385cd2a6e2 --- /dev/null +++ b/spec/ruby/library/yaml/unsafe_load_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'shared/load' + +guard -> { Psych::VERSION >= "4.0.0" } do + describe "YAML.unsafe_load" do + it_behaves_like :yaml_load_safe, :unsafe_load + it_behaves_like :yaml_load_unsafe, :unsafe_load + end +end diff --git a/spec/ruby/library/zlib/adler32_spec.rb b/spec/ruby/library/zlib/adler32_spec.rb new file mode 100644 index 0000000000..226aa18522 --- /dev/null +++ b/spec/ruby/library/zlib/adler32_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' +require 'zlib' + +describe "Zlib.adler32" do + it "calculates Adler checksum for string" do + Zlib.adler32("").should == 1 + Zlib.adler32(" ").should == 2162721 + Zlib.adler32("123456789").should == 152961502 + Zlib.adler32("!@#\{$\}%^&**()").should == 365495023 + Zlib.adler32("to be or not to be" * 22).should == 3979904837 + Zlib.adler32("0").should == 3211313 + Zlib.adler32((2**32).to_s).should == 193331739 + Zlib.adler32((2**64).to_s).should == 723452953 + end + + it "calculates Adler checksum for string and initial Adler value" do + test_string = "This is a test string! How exciting!%?" + Zlib.adler32(test_string, 0).should == 63900955 + Zlib.adler32(test_string, 1).should == 66391324 + Zlib.adler32(test_string, 2**8).should == 701435419 + Zlib.adler32(test_string, 2**16).should == 63966491 + -> { Zlib.adler32(test_string, 2**128) }.should raise_error(RangeError) + end + + it "calculates the Adler checksum for string and initial Adler value for Integers" do + test_string = "This is a test string! How exciting!%?" + Zlib.adler32(test_string, 2**30).should == 1137642779 + end + + it "assumes that the initial value is given to adler, if adler is omitted" do + orig_crc = Zlib.adler32 + Zlib.adler32("").should == Zlib.adler32("", orig_crc) + Zlib.adler32(" ").should == Zlib.adler32(" ", orig_crc) + Zlib.adler32("123456789").should == Zlib.adler32("123456789", orig_crc) + Zlib.adler32("!@#\{$\}%^&**()").should == Zlib.adler32("!@#\{$\}%^&**()", orig_crc) + Zlib.adler32("to be or not to be" * 22).should == Zlib.adler32("to be or not to be" * 22, orig_crc) + Zlib.adler32("0").should == Zlib.adler32("0", orig_crc) + Zlib.adler32((2**32).to_s).should == Zlib.adler32((2**32).to_s, orig_crc) + Zlib.adler32((2**64).to_s).should == Zlib.adler32((2**64).to_s, orig_crc) + end + + it "it returns the CRC initial value, if string is omitted" do + Zlib.adler32.should == 1 + end + +end diff --git a/spec/ruby/library/zlib/crc32_spec.rb b/spec/ruby/library/zlib/crc32_spec.rb new file mode 100644 index 0000000000..d5f5c199cc --- /dev/null +++ b/spec/ruby/library/zlib/crc32_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require 'zlib' + +describe "Zlib.crc32" do + it "calculates CRC checksum for string" do + Zlib.crc32("").should == 0 + Zlib.crc32(" ").should == 3916222277 + Zlib.crc32("123456789").should == 3421780262 + Zlib.crc32("!@#\{$\}%^&**()").should == 2824518887 + Zlib.crc32("to be or not to be" * 22).should == 1832379978 + Zlib.crc32("0").should == 4108050209 + Zlib.crc32((2**32).to_s).should == 3267533297 + Zlib.crc32((2**64).to_s).should == 653721760 + end + + it "calculates CRC checksum for string and initial CRC value" do + test_string = "This is a test string! How exciting!%?" + # Zlib.crc32(test_string, -2**28).should == 3230195786 + # Zlib.crc32(test_string, -2**20).should == 2770207303 + # Zlib.crc32(test_string, -2**16).should == 2299432960 + # Zlib.crc32(test_string, -2**8).should == 861809849 + # Zlib.crc32(test_string, -1).should == 2170124077 + Zlib.crc32(test_string, 0).should == 3864990561 + Zlib.crc32(test_string, 1).should == 1809313411 + Zlib.crc32(test_string, 2**8).should == 1722745982 + Zlib.crc32(test_string, 2**16).should == 1932511220 + Zlib.crc32("p", ~305419896).should == 4046865307 + Zlib.crc32("p", -305419897).should == 4046865307 + -> { Zlib.crc32(test_string, 2**128) }.should raise_error(RangeError) + end + + it "calculates the CRC checksum for string and initial CRC value for Integers" do + test_string = "This is a test string! How exciting!%?" + # Zlib.crc32(test_string, -2**30).should == 277228695 + Zlib.crc32(test_string, 2**30).should == 46597132 + end + + it "assumes that the initial value is given to crc, if crc is omitted" do + orig_crc = Zlib.crc32 + Zlib.crc32("").should == Zlib.crc32("", orig_crc) + Zlib.crc32(" ").should == Zlib.crc32(" ", orig_crc) + Zlib.crc32("123456789").should == Zlib.crc32("123456789", orig_crc) + Zlib.crc32("!@#\{$\}%^&**()").should == Zlib.crc32("!@#\{$\}%^&**()", orig_crc) + Zlib.crc32("to be or not to be" * 22).should == Zlib.crc32("to be or not to be" * 22, orig_crc) + Zlib.crc32("0").should == Zlib.crc32("0", orig_crc) + Zlib.crc32((2**32).to_s).should == Zlib.crc32((2**32).to_s, orig_crc) + Zlib.crc32((2**64).to_s).should == Zlib.crc32((2**64).to_s, orig_crc) + end + + it "it returns the CRC initial value, if string is omitted" do + Zlib.crc32.should == 0 + end + +end diff --git a/spec/ruby/library/zlib/crc_table_spec.rb b/spec/ruby/library/zlib/crc_table_spec.rb new file mode 100644 index 0000000000..de8876086b --- /dev/null +++ b/spec/ruby/library/zlib/crc_table_spec.rb @@ -0,0 +1,80 @@ +require_relative '../../spec_helper' +require "zlib" + +describe "Zlib.crc_table" do + # This spec fails when zlib.h and libz.so are not from the same version. + # In older zlib (< 1.2.7 it seems), get_crc_table() is stored as u64[], + # but in newer zlib, get_crc_table() is stored as u32[]. + # Technically, there is ABI breakage between those zlib versions, + # but get_crc_table() is an "undocumented function" according to zlib.h. + guard -> { ENV["RUBY_SPEC_TEST_ZLIB_CRC_TABLE"] != "false" } do + it "returns the same value as zlib's get_crc_table()" do + Zlib.crc_table.should == [ + 0, 1996959894, 3993919788, 2567524794, + 124634137, 1886057615, 3915621685, 2657392035, + 249268274, 2044508324, 3772115230, 2547177864, + 162941995, 2125561021, 3887607047, 2428444049, + 498536548, 1789927666, 4089016648, 2227061214, + 450548861, 1843258603, 4107580753, 2211677639, + 325883990, 1684777152, 4251122042, 2321926636, + 335633487, 1661365465, 4195302755, 2366115317, + 997073096, 1281953886, 3579855332, 2724688242, + 1006888145, 1258607687, 3524101629, 2768942443, + 901097722, 1119000684, 3686517206, 2898065728, + 853044451, 1172266101, 3705015759, 2882616665, + 651767980, 1373503546, 3369554304, 3218104598, + 565507253, 1454621731, 3485111705, 3099436303, + 671266974, 1594198024, 3322730930, 2970347812, + 795835527, 1483230225, 3244367275, 3060149565, + 1994146192, 31158534, 2563907772, 4023717930, + 1907459465, 112637215, 2680153253, 3904427059, + 2013776290, 251722036, 2517215374, 3775830040, + 2137656763, 141376813, 2439277719, 3865271297, + 1802195444, 476864866, 2238001368, 4066508878, + 1812370925, 453092731, 2181625025, 4111451223, + 1706088902, 314042704, 2344532202, 4240017532, + 1658658271, 366619977, 2362670323, 4224994405, + 1303535960, 984961486, 2747007092, 3569037538, + 1256170817, 1037604311, 2765210733, 3554079995, + 1131014506, 879679996, 2909243462, 3663771856, + 1141124467, 855842277, 2852801631, 3708648649, + 1342533948, 654459306, 3188396048, 3373015174, + 1466479909, 544179635, 3110523913, 3462522015, + 1591671054, 702138776, 2966460450, 3352799412, + 1504918807, 783551873, 3082640443, 3233442989, + 3988292384, 2596254646, 62317068, 1957810842, + 3939845945, 2647816111, 81470997, 1943803523, + 3814918930, 2489596804, 225274430, 2053790376, + 3826175755, 2466906013, 167816743, 2097651377, + 4027552580, 2265490386, 503444072, 1762050814, + 4150417245, 2154129355, 426522225, 1852507879, + 4275313526, 2312317920, 282753626, 1742555852, + 4189708143, 2394877945, 397917763, 1622183637, + 3604390888, 2714866558, 953729732, 1340076626, + 3518719985, 2797360999, 1068828381, 1219638859, + 3624741850, 2936675148, 906185462, 1090812512, + 3747672003, 2825379669, 829329135, 1181335161, + 3412177804, 3160834842, 628085408, 1382605366, + 3423369109, 3138078467, 570562233, 1426400815, + 3317316542, 2998733608, 733239954, 1555261956, + 3268935591, 3050360625, 752459403, 1541320221, + 2607071920, 3965973030, 1969922972, 40735498, + 2617837225, 3943577151, 1913087877, 83908371, + 2512341634, 3803740692, 2075208622, 213261112, + 2463272603, 3855990285, 2094854071, 198958881, + 2262029012, 4057260610, 1759359992, 534414190, + 2176718541, 4139329115, 1873836001, 414664567, + 2282248934, 4279200368, 1711684554, 285281116, + 2405801727, 4167216745, 1634467795, 376229701, + 2685067896, 3608007406, 1308918612, 956543938, + 2808555105, 3495958263, 1231636301, 1047427035, + 2932959818, 3654703836, 1088359270, 936918000, + 2847714899, 3736837829, 1202900863, 817233897, + 3183342108, 3401237130, 1404277552, 615818150, + 3134207493, 3453421203, 1423857449, 601450431, + 3009837614, 3294710456, 1567103746, 711928724, + 3020668471, 3272380065, 1510334235, 755167117, + ] + end + end +end diff --git a/spec/ruby/library/zlib/deflate/deflate_spec.rb b/spec/ruby/library/zlib/deflate/deflate_spec.rb new file mode 100644 index 0000000000..e16e6ad0ef --- /dev/null +++ b/spec/ruby/library/zlib/deflate/deflate_spec.rb @@ -0,0 +1,133 @@ +require 'zlib' +require_relative '../../../spec_helper' + +describe "Zlib::Deflate.deflate" do + it "deflates some data" do + data = Array.new(10,0).pack('C*') + + zipped = Zlib::Deflate.deflate data + + zipped.should == [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*') + end + + it "deflates lots of data" do + data = "\000" * 32 * 1024 + + zipped = Zlib::Deflate.deflate data + + zipped.should == ([120, 156, 237, 193, 1, 1, 0, 0] + + [0, 128, 144, 254, 175, 238, 8, 10] + + Array.new(31, 0) + + [24, 128, 0, 0, 1]).pack('C*') + end + + it "deflates chunked data" do + random_generator = Random.new(0) + deflated = +'' + + Zlib::Deflate.deflate(random_generator.bytes(20000)) do |chunk| + deflated << chunk + end + + deflated.length.should == 20016 + end +end + +describe "Zlib::Deflate#deflate" do + before :each do + @deflator = Zlib::Deflate.new + end + + it "deflates some data" do + data = "\000" * 10 + + zipped = @deflator.deflate data, Zlib::FINISH + @deflator.finish + + zipped.should == [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*') + end + + it "deflates lots of data" do + data = "\000" * 32 * 1024 + + zipped = @deflator.deflate data, Zlib::FINISH + @deflator.finish + + zipped.should == ([120, 156, 237, 193, 1, 1, 0, 0] + + [0, 128, 144, 254, 175, 238, 8, 10] + + Array.new(31, 0) + + [24, 128, 0, 0, 1]).pack('C*') + end + + it "has a binary encoding" do + @deflator.deflate("").encoding.should == Encoding::BINARY + @deflator.finish.encoding.should == Encoding::BINARY + end +end + +describe "Zlib::Deflate#deflate" do + + before :each do + @deflator = Zlib::Deflate.new + @random_generator = Random.new(0) + @original = +'' + @chunks = [] + end + + describe "without break" do + + before do + 2.times do + @input = @random_generator.bytes(20000) + @original << @input + @deflator.deflate(@input) do |chunk| + @chunks << chunk + end + end + end + + it "deflates chunked data" do + @deflator.finish + @chunks.map { |chunk| chunk.length }.should == [16384, 16384] + end + + it "deflates chunked data with final chunk" do + final = @deflator.finish + final.length.should == 7253 + end + + it "deflates chunked data without errors" do + final = @deflator.finish + @chunks << final + @original.should == Zlib.inflate(@chunks.join) + end + + end + + describe "with break" do + before :each do + @input = @random_generator.bytes(20000) + @deflator.deflate(@input) do |chunk| + @chunks << chunk + break + end + end + + it "deflates only first chunk" do + @deflator.finish + @chunks.map { |chunk| chunk.length }.should == [16384] + end + + it "deflates chunked data with final chunk" do + final = @deflator.finish + final.length.should == 3632 + end + + it "deflates chunked data without errors" do + final = @deflator.finish + @chunks << final + @input.should == Zlib.inflate(@chunks.join) + end + + end +end diff --git a/spec/ruby/library/zlib/deflate/params_spec.rb b/spec/ruby/library/zlib/deflate/params_spec.rb new file mode 100644 index 0000000000..0242653528 --- /dev/null +++ b/spec/ruby/library/zlib/deflate/params_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::Deflate#params" do + it "changes the deflate parameters" do + data = +'abcdefghijklm' + + d = Zlib::Deflate.new Zlib::NO_COMPRESSION, Zlib::MAX_WBITS, + Zlib::DEF_MEM_LEVEL, Zlib::DEFAULT_STRATEGY + + d << data.slice!(0..10) + d.params Zlib::BEST_COMPRESSION, Zlib::DEFAULT_STRATEGY + d << data + + Zlib::Inflate.inflate(d.finish).should == 'abcdefghijklm' + end +end diff --git a/spec/ruby/library/zlib/deflate/set_dictionary_spec.rb b/spec/ruby/library/zlib/deflate/set_dictionary_spec.rb new file mode 100644 index 0000000000..0e461229c7 --- /dev/null +++ b/spec/ruby/library/zlib/deflate/set_dictionary_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::Deflate#set_dictionary" do + it "sets the dictionary" do + d = Zlib::Deflate.new + d.set_dictionary 'aaaaaaaaaa' + d << 'abcdefghij' + + d.finish.should == [120, 187, 20, 225, 3, 203, 75, 76, + 74, 78, 73, 77, 75, 207, 200, 204, + 2, 0, 21, 134, 3, 248].pack('C*') + end +end diff --git a/spec/ruby/library/zlib/deflate_spec.rb b/spec/ruby/library/zlib/deflate_spec.rb new file mode 100644 index 0000000000..6eeaa164c5 --- /dev/null +++ b/spec/ruby/library/zlib/deflate_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require "zlib" + +describe "Zlib.deflate" do + it "deflates some data" do + Zlib.deflate("1" * 10).should == [120, 156, 51, 52, 132, 1, 0, 10, 145, 1, 235].pack('C*') + end +end diff --git a/spec/ruby/library/zlib/gunzip_spec.rb b/spec/ruby/library/zlib/gunzip_spec.rb new file mode 100644 index 0000000000..2417fed57c --- /dev/null +++ b/spec/ruby/library/zlib/gunzip_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' +require 'zlib' + +describe "Zlib.gunzip" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + end + + it "decodes the given gzipped string" do + Zlib.gunzip(@zip).should == @data + end +end diff --git a/spec/ruby/library/zlib/gzip_spec.rb b/spec/ruby/library/zlib/gzip_spec.rb new file mode 100644 index 0000000000..35694264f0 --- /dev/null +++ b/spec/ruby/library/zlib/gzip_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require 'zlib' + +describe "Zlib.gzip" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + end + + it "gzips the given string" do + # skip gzip header for now + Zlib.gzip(@data)[10..-1].should == @zip[10..-1] + end +end diff --git a/spec/ruby/library/zlib/gzipfile/close_spec.rb b/spec/ruby/library/zlib/gzipfile/close_spec.rb new file mode 100644 index 0000000000..964b5ffb4d --- /dev/null +++ b/spec/ruby/library/zlib/gzipfile/close_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipFile#close" do + it "finishes the stream and closes the io" do + io = StringIO.new "".b + Zlib::GzipWriter.wrap io do |gzio| + gzio.close + + gzio.should.closed? + + -> { gzio.orig_name }.should \ + raise_error(Zlib::GzipFile::Error, 'closed gzip stream') + -> { gzio.comment }.should \ + raise_error(Zlib::GzipFile::Error, 'closed gzip stream') + end + + io.string[10..-1].should == ([3] + Array.new(9,0)).pack('C*') + end +end diff --git a/spec/ruby/library/zlib/gzipfile/closed_spec.rb b/spec/ruby/library/zlib/gzipfile/closed_spec.rb new file mode 100644 index 0000000000..726f391b41 --- /dev/null +++ b/spec/ruby/library/zlib/gzipfile/closed_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipFile#closed?" do + it "returns the closed status" do + io = StringIO.new + Zlib::GzipWriter.wrap io do |gzio| + gzio.should_not.closed? + + gzio.close + + gzio.should.closed? + end + end +end diff --git a/spec/ruby/library/zlib/gzipfile/comment_spec.rb b/spec/ruby/library/zlib/gzipfile/comment_spec.rb new file mode 100644 index 0000000000..70d97ecaf6 --- /dev/null +++ b/spec/ruby/library/zlib/gzipfile/comment_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipFile#comment" do + before :each do + @io = StringIO.new + end + + it "returns the name" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.comment = 'name' + + gzio.comment.should == 'name' + end + end + + it "raises an error on a closed stream" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.close + + -> { gzio.comment }.should \ + raise_error(Zlib::GzipFile::Error, 'closed gzip stream') + end + end +end diff --git a/spec/ruby/library/zlib/gzipfile/orig_name_spec.rb b/spec/ruby/library/zlib/gzipfile/orig_name_spec.rb new file mode 100644 index 0000000000..ebfd3692af --- /dev/null +++ b/spec/ruby/library/zlib/gzipfile/orig_name_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipFile#orig_name" do + before :each do + @io = StringIO.new + end + + it "returns the name" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.orig_name = 'name' + + gzio.orig_name.should == 'name' + end + end + + it "raises an error on a closed stream" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.close + + -> { gzio.orig_name }.should \ + raise_error(Zlib::GzipFile::Error, 'closed gzip stream') + end + end +end diff --git a/spec/ruby/library/zlib/gzipreader/each_byte_spec.rb b/spec/ruby/library/zlib/gzipreader/each_byte_spec.rb new file mode 100644 index 0000000000..48821dc833 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/each_byte_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#each_byte" do + + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + + @io = StringIO.new @zip + ScratchPad.clear + end + + it "calls the given block for each byte in the stream, passing the byte as an argument" do + gz = Zlib::GzipReader.new @io + + ScratchPad.record [] + gz.each_byte { |b| ScratchPad << b } + + ScratchPad.recorded.should == [49, 50, 51, 52, 53, 97, 98, 99, 100, 101] + end + + it "returns an enumerator, which yields each byte in the stream, when no block is passed" do + gz = Zlib::GzipReader.new @io + enum = gz.each_byte + + ScratchPad.record [] + while true + begin + ScratchPad << enum.next + rescue StopIteration + break + end + end + + ScratchPad.recorded.should == [49, 50, 51, 52, 53, 97, 98, 99, 100, 101] + end + + it "increments position before calling the block" do + gz = Zlib::GzipReader.new @io + + i = 1 + gz.each_byte do |ignore| + gz.pos.should == i + i += 1 + end + end + +end diff --git a/spec/ruby/library/zlib/gzipreader/each_char_spec.rb b/spec/ruby/library/zlib/gzipreader/each_char_spec.rb new file mode 100644 index 0000000000..de6396da7e --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/each_char_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#each_char" do + + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + + @io = StringIO.new @zip + ScratchPad.clear + end + + it "calls the given block for each char in the stream, passing the char as an argument" do + gz = Zlib::GzipReader.new @io + + ScratchPad.record [] + gz.each_char { |b| ScratchPad << b } + + ScratchPad.recorded.should == ["1", "2", "3", "4", "5", "a", "b", "c", "d", "e"] + end + + it "returns an enumerator, which yields each char in the stream, when no block is passed" do + gz = Zlib::GzipReader.new @io + enum = gz.each_char + + ScratchPad.record [] + while true + begin + ScratchPad << enum.next + rescue StopIteration + break + end + end + + ScratchPad.recorded.should == ["1", "2", "3", "4", "5", "a", "b", "c", "d", "e"] + end + + it "increments position before calling the block" do + gz = Zlib::GzipReader.new @io + + i = 1 + gz.each_char do |ignore| + gz.pos.should == i + i += 1 + end + end + +end diff --git a/spec/ruby/library/zlib/gzipreader/each_line_spec.rb b/spec/ruby/library/zlib/gzipreader/each_line_spec.rb new file mode 100644 index 0000000000..6f17365879 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/each_line_spec.rb @@ -0,0 +1,6 @@ +require_relative "../../../spec_helper" +require_relative 'shared/each' + +describe "Zlib::GzipReader#each_line" do + it_behaves_like :gzipreader_each, :each_line +end diff --git a/spec/ruby/library/zlib/gzipreader/each_spec.rb b/spec/ruby/library/zlib/gzipreader/each_spec.rb new file mode 100644 index 0000000000..3b98391a87 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/each_spec.rb @@ -0,0 +1,6 @@ +require_relative "../../../spec_helper" +require_relative 'shared/each' + +describe "Zlib::GzipReader#each" do + it_behaves_like :gzipreader_each, :each +end diff --git a/spec/ruby/library/zlib/gzipreader/eof_spec.rb b/spec/ruby/library/zlib/gzipreader/eof_spec.rb new file mode 100644 index 0000000000..673220fdfd --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/eof_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#eof?" do + before :each do + @data = '{"a":1234}' + @zip = [31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 171, 86, 74, 84, 178, 50, + 52, 50, 54, 169, 5, 0, 196, 20, 118, 213, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + end + + it "returns true when at EOF" do + gz = Zlib::GzipReader.new @io + gz.eof?.should be_false + gz.read + gz.eof?.should be_true + end + + it "returns true when at EOF with the exact length of uncompressed data" do + gz = Zlib::GzipReader.new @io + gz.eof?.should be_false + gz.read(10) + gz.eof?.should be_true + end + + it "returns true when at EOF with a length greater than the size of uncompressed data" do + gz = Zlib::GzipReader.new @io + gz.eof?.should be_false + gz.read(11) + gz.eof?.should be_true + end + + it "returns false when at EOF when there's data left in the buffer to read" do + gz = Zlib::GzipReader.new @io + gz.read(9) + gz.eof?.should be_false + gz.read + gz.eof?.should be_true + end + + # This is especially important for JRuby, since eof? there + # is more than just a simple accessor. + it "does not affect the reading data" do + gz = Zlib::GzipReader.new @io + 0.upto(9) do |i| + gz.eof?.should be_false + gz.read(1).should == @data[i, 1] + end + gz.eof?.should be_true + gz.read.should == "" + gz.eof?.should be_true + end +end diff --git a/spec/ruby/library/zlib/gzipreader/getc_spec.rb b/spec/ruby/library/zlib/gzipreader/getc_spec.rb new file mode 100644 index 0000000000..e567231940 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/getc_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#getc" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + end + + it "returns the next character from the stream" do + gz = Zlib::GzipReader.new @io + gz.pos.should == 0 + + gz.getc.should == '1' + gz.getc.should == '2' + gz.getc.should == '3' + gz.getc.should == '4' + gz.getc.should == '5' + end + + it "increments position" do + gz = Zlib::GzipReader.new @io + (0..@data.size).each do |i| + gz.pos.should == i + gz.getc + end + end + + it "returns nil at the end of the stream" do + gz = Zlib::GzipReader.new @io + gz.read + pos = gz.pos + gz.getc.should be_nil + gz.pos.should == pos + end +end diff --git a/spec/ruby/library/zlib/gzipreader/gets_spec.rb b/spec/ruby/library/zlib/gzipreader/gets_spec.rb new file mode 100644 index 0000000000..d3a2e7d263 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/gets_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../../spec_helper' +require 'zlib' +require 'stringio' + +describe 'Zlib::GzipReader#gets' do + describe 'with "" separator' do + it 'reads paragraphs skipping newlines' do + # gz contains "\n\n\n\n\n123\n45\n\n\n\n\nabc\nde\n\n\n\n\n" + gz = Zlib::GzipReader.new( + StringIO.new( + [31, 139, 8, 0, 223, 152, 48, 89, 0, 3, 227, 226, 2, 2, 67, 35, + 99, 46, 19, 83, 16, 139, 43, 49, 41, 153, 43, 37, 21, 204, 4, 0, + 32, 119, 45, 184, 27, 0, 0, 0].pack('C*') + ) + ) + + gz.gets('').should == "123\n45\n\n" + gz.gets('').should == "abc\nde\n\n" + gz.eof?.should be_true + end + end +end diff --git a/spec/ruby/library/zlib/gzipreader/mtime_spec.rb b/spec/ruby/library/zlib/gzipreader/mtime_spec.rb new file mode 100644 index 0000000000..e8e71fa72e --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/mtime_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require 'zlib' +require 'stringio' + +describe "Zlib::GzipReader#mtime" do + it "returns the timestamp from the Gzip header" do + io = StringIO.new "\x1f\x8b\x08\x00\x44\x33\x22\x11\x00\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00" + gz = Zlib::GzipReader.new(io) + gz.mtime.to_i.should == 0x11223344 + end +end diff --git a/spec/ruby/library/zlib/gzipreader/pos_spec.rb b/spec/ruby/library/zlib/gzipreader/pos_spec.rb new file mode 100644 index 0000000000..8586faec92 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/pos_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#pos" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + end + + it "returns the position" do + gz = Zlib::GzipReader.new @io + + gz.pos.should == 0 + + gz.read 5 + gz.pos.should == 5 + + gz.read + gz.pos.should == @data.length + end +end diff --git a/spec/ruby/library/zlib/gzipreader/read_spec.rb b/spec/ruby/library/zlib/gzipreader/read_spec.rb new file mode 100644 index 0000000000..b81954b5ce --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/read_spec.rb @@ -0,0 +1,66 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#read" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + end + + it "with no arguments reads the entire content of a gzip file" do + gz = Zlib::GzipReader.new @io + gz.read.should == @data + end + + it "with nil length argument reads the entire content of a gzip file" do + gz = Zlib::GzipReader.new @io + gz.read(nil).should == @data + end + + it "reads the contents up to a certain size" do + gz = Zlib::GzipReader.new @io + gz.read(5).should == @data[0...5] + gz.read(5).should == @data[5...10] + end + + it "does not accept a negative length to read" do + gz = Zlib::GzipReader.new @io + -> { + gz.read(-1) + }.should raise_error(ArgumentError) + end + + it "returns an empty string if a 0 length is given" do + gz = Zlib::GzipReader.new @io + gz.read(0).should == "" + end + + it "respects :external_encoding option" do + gz = Zlib::GzipReader.new(@io, external_encoding: 'UTF-8') + gz.read.encoding.should == Encoding::UTF_8 + + @io.rewind + gz = Zlib::GzipReader.new(@io, external_encoding: 'UTF-16LE') + gz.read.encoding.should == Encoding::UTF_16LE + end + + describe "at the end of data" do + it "returns empty string if length parameter is not specified or 0" do + gz = Zlib::GzipReader.new @io + gz.read # read till the end + gz.read(0).should == "" + gz.read().should == "" + gz.read(nil).should == "" + end + + it "returns nil if length parameter is positive" do + gz = Zlib::GzipReader.new @io + gz.read # read till the end + gz.read(1).should be_nil + gz.read(2**16).should be_nil + end + end +end diff --git a/spec/ruby/library/zlib/gzipreader/readpartial_spec.rb b/spec/ruby/library/zlib/gzipreader/readpartial_spec.rb new file mode 100644 index 0000000000..559ce9f841 --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/readpartial_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#readpartial" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new(@zip) + end + + it 'accepts nil buffer' do + gz = Zlib::GzipReader.new(@io) + gz.readpartial(5, nil).should == '12345' + end +end diff --git a/spec/ruby/library/zlib/gzipreader/rewind_spec.rb b/spec/ruby/library/zlib/gzipreader/rewind_spec.rb new file mode 100644 index 0000000000..b31abb6abf --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/rewind_spec.rb @@ -0,0 +1,47 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#rewind" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + ScratchPad.clear + end + + it "resets the position of the stream pointer" do + gz = Zlib::GzipReader.new @io + gz.read + gz.pos.should == @data.length + + gz.rewind + gz.pos.should == 0 + gz.lineno.should == 0 + end + + it "resets the position of the stream pointer to data previously read" do + gz = Zlib::GzipReader.new @io + first_read = gz.read + gz.rewind + first_read.should == gz.read + end + + it "invokes seek method on the associated IO object" do + # first, prepare the mock object: + (obj = mock("io")).should_receive(:get_io).any_number_of_times.and_return(@io) + def obj.read(args); get_io.read(args); end + def obj.seek(pos, whence = 0) + ScratchPad.record :seek + get_io.seek(pos, whence) + end + + gz = Zlib::GzipReader.new(obj) + gz.rewind() + + ScratchPad.recorded.should == :seek + gz.pos.should == 0 + gz.read.should == "12345abcde" + end +end diff --git a/spec/ruby/library/zlib/gzipreader/shared/each.rb b/spec/ruby/library/zlib/gzipreader/shared/each.rb new file mode 100644 index 0000000000..71608e04ab --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/shared/each.rb @@ -0,0 +1,49 @@ +require_relative '../../../../spec_helper' +require 'stringio' +require 'zlib' + +describe :gzipreader_each, shared: true do + before :each do + @data = "firstline\nsecondline\n\nforthline" + @zip = [31, 139, 8, 0, 244, 125, 128, 88, 2, 255, 75, 203, 44, 42, 46, 201, + 201, 204, 75, 229, 42, 78, 77, 206, 207, 75, 1, 51, 185, 210,242, + 139, 74, 50, 64, 76, 0, 180, 54, 61, 111, 31, 0, 0, 0].pack('C*') + + @io = StringIO.new @zip + @gzreader = Zlib::GzipReader.new @io + end + + after :each do + ScratchPad.clear + end + + it "calls the given block for each line in the stream, passing the line as an argument" do + ScratchPad.record [] + @gzreader.send(@method) { |b| ScratchPad << b } + + ScratchPad.recorded.should == ["firstline\n", "secondline\n", "\n", "forthline"] + end + + it "returns an enumerator, which yields each byte in the stream, when no block is passed" do + enum = @gzreader.send(@method) + + ScratchPad.record [] + while true + begin + ScratchPad << enum.next + rescue StopIteration + break + end + end + + ScratchPad.recorded.should == ["firstline\n", "secondline\n", "\n", "forthline"] + end + + it "increments position before calling the block" do + i = 0 + @gzreader.send(@method) do |line| + i += line.length + @gzreader.pos.should == i + end + end +end diff --git a/spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb b/spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb new file mode 100644 index 0000000000..7fa0608f9f --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb @@ -0,0 +1,120 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#ungetbyte" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + end + + describe 'at the start of the stream' do + before :each do + @gz = Zlib::GzipReader.new(@io) + end + + describe 'with an integer' do + it 'prepends the byte to the stream' do + @gz.ungetbyte 0x21 + @gz.read.should == '!12345abcde' + end + + it 'decrements pos' do + @gz.ungetbyte 0x21 + @gz.pos.should == -1 + end + end + + quarantine! do # https://bugs.ruby-lang.org/issues/13675 + describe 'with nil' do + it 'does not prepend anything to the stream' do + @gz.ungetbyte nil + @gz.read.should == '12345abcde' + end + + it 'does not decrement pos' do + @gz.ungetbyte nil + @gz.pos.should == 0 + end + end + end + end + + describe 'in the middle of the stream' do + before :each do + @gz = Zlib::GzipReader.new(@io) + @gz.read 5 + end + + describe 'with an integer' do + it 'inserts the corresponding character into the stream' do + @gz.ungetbyte 0x21 + @gz.read.should == '!abcde' + end + + it 'decrements pos' do + @gz.ungetbyte 0x21 + @gz.pos.should == 4 + end + end + + quarantine! do # https://bugs.ruby-lang.org/issues/13675 + describe 'with nil' do + it 'does not insert anything into the stream' do + @gz.ungetbyte nil + @gz.read.should == 'abcde' + end + + it 'does not decrement pos' do + @gz.ungetbyte nil + @gz.pos.should == 5 + end + end + end + end + + describe 'at the end of the stream' do + before :each do + @gz = Zlib::GzipReader.new(@io) + @gz.read + end + + describe 'with an integer' do + it 'appends the corresponding character to the stream' do + @gz.ungetbyte 0x21 + @gz.read.should == '!' + end + + it 'decrements pos' do + @gz.ungetbyte 0x21 + @gz.pos.should == 9 + end + + it 'makes eof? false' do + @gz.ungetbyte 0x21 + @gz.eof?.should be_false + end + end + + quarantine! do # https://bugs.ruby-lang.org/issues/13675 + describe 'with nil' do + it 'does not append anything to the stream' do + @gz.ungetbyte nil + @gz.read.should == '' + end + + it 'does not decrement pos' do + @gz.ungetbyte nil + @gz.pos.should == 10 + end + + it 'does not make eof? false' do + @gz.ungetbyte nil + @gz.eof?.should be_true + end + end + end + end +end diff --git a/spec/ruby/library/zlib/gzipreader/ungetc_spec.rb b/spec/ruby/library/zlib/gzipreader/ungetc_spec.rb new file mode 100644 index 0000000000..34f2a1a2ca --- /dev/null +++ b/spec/ruby/library/zlib/gzipreader/ungetc_spec.rb @@ -0,0 +1,284 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipReader#ungetc" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new @zip + end + + describe 'at the start of the stream' do + before :each do + @gz = Zlib::GzipReader.new(@io, external_encoding: Encoding::UTF_8) + end + + describe 'with a single-byte character' do + it 'prepends the character to the stream' do + @gz.ungetc 'x' + @gz.read.should == 'x12345abcde' + end + + it 'decrements pos' do + @gz.ungetc 'x' + @gz.pos.should == -1 + end + end + + describe 'with a multi-byte character' do + it 'prepends the character to the stream' do + @gz.ungetc 'ŷ' + @gz.read.should == 'ŷ12345abcde' + end + + it 'decrements pos' do + @gz.ungetc 'ŷ' + @gz.pos.should == -2 + end + end + + describe 'with a multi-character string' do + it 'prepends the characters to the stream' do + @gz.ungetc 'xŷž' + @gz.read.should == 'xŷž12345abcde' + end + + it 'decrements pos' do + @gz.ungetc 'xŷž' + @gz.pos.should == -5 + end + end + + describe 'with an integer' do + it 'prepends the corresponding character to the stream' do + @gz.ungetc 0x21 + @gz.read.should == '!12345abcde' + end + + it 'decrements pos' do + @gz.ungetc 0x21 + @gz.pos.should == -1 + end + end + + describe 'with an empty string' do + it 'does not prepend anything to the stream' do + @gz.ungetc '' + @gz.read.should == '12345abcde' + end + + it 'does not decrement pos' do + @gz.ungetc '' + @gz.pos.should == 0 + end + end + + quarantine! do # https://bugs.ruby-lang.org/issues/13675 + describe 'with nil' do + it 'does not prepend anything to the stream' do + @gz.ungetc nil + @gz.read.should == '12345abcde' + end + + it 'does not decrement pos' do + @gz.ungetc nil + @gz.pos.should == 0 + end + end + end + end + + describe 'in the middle of the stream' do + before :each do + @gz = Zlib::GzipReader.new(@io, external_encoding: Encoding::UTF_8) + @gz.read 5 + end + + describe 'with a single-byte character' do + it 'inserts the character into the stream' do + @gz.ungetc 'x' + @gz.read.should == 'xabcde' + end + + it 'decrements pos' do + @gz.ungetc 'x' + @gz.pos.should == 4 + end + end + + describe 'with a multi-byte character' do + it 'inserts the character into the stream' do + @gz.ungetc 'ŷ' + @gz.read.should == 'ŷabcde' + end + + it 'decrements pos' do + @gz.ungetc 'ŷ' + @gz.pos.should == 3 + end + end + + describe 'with a multi-character string' do + it 'inserts the characters into the stream' do + @gz.ungetc 'xŷž' + @gz.read.should == 'xŷžabcde' + end + + it 'decrements pos' do + @gz.ungetc 'xŷž' + @gz.pos.should == 0 + end + end + + describe 'with an integer' do + it 'inserts the corresponding character into the stream' do + @gz.ungetc 0x21 + @gz.read.should == '!abcde' + end + + it 'decrements pos' do + @gz.ungetc 0x21 + @gz.pos.should == 4 + end + end + + describe 'with an empty string' do + it 'does not insert anything into the stream' do + @gz.ungetc '' + @gz.read.should == 'abcde' + end + + it 'does not decrement pos' do + @gz.ungetc '' + @gz.pos.should == 5 + end + end + + quarantine! do # https://bugs.ruby-lang.org/issues/13675 + describe 'with nil' do + it 'does not insert anything into the stream' do + @gz.ungetc nil + @gz.read.should == 'abcde' + end + + it 'does not decrement pos' do + @gz.ungetc nil + @gz.pos.should == 5 + end + end + end + end + + describe 'at the end of the stream' do + before :each do + @gz = Zlib::GzipReader.new(@io, external_encoding: Encoding::UTF_8) + @gz.read + end + + describe 'with a single-byte character' do + it 'appends the character to the stream' do + @gz.ungetc 'x' + @gz.read.should == 'x' + end + + it 'decrements pos' do + @gz.ungetc 'x' + @gz.pos.should == 9 + end + + it 'makes eof? false' do + @gz.ungetc 'x' + @gz.eof?.should be_false + end + end + + describe 'with a multi-byte character' do + it 'appends the character to the stream' do + @gz.ungetc 'ŷ' + @gz.read.should == 'ŷ' + end + + it 'decrements pos' do + @gz.ungetc 'ŷ' + @gz.pos.should == 8 + end + + it 'makes eof? false' do + @gz.ungetc 'ŷ' + @gz.eof?.should be_false + end + end + + describe 'with a multi-character string' do + it 'appends the characters to the stream' do + @gz.ungetc 'xŷž' + @gz.read.should == 'xŷž' + end + + it 'decrements pos' do + @gz.ungetc 'xŷž' + @gz.pos.should == 5 + end + + it 'makes eof? false' do + @gz.ungetc 'xŷž' + @gz.eof?.should be_false + end + end + + describe 'with an integer' do + it 'appends the corresponding character to the stream' do + @gz.ungetc 0x21 + @gz.read.should == '!' + end + + it 'decrements pos' do + @gz.ungetc 0x21 + @gz.pos.should == 9 + end + + it 'makes eof? false' do + @gz.ungetc 0x21 + @gz.eof?.should be_false + end + end + + describe 'with an empty string' do + it 'does not append anything to the stream' do + @gz.ungetc '' + @gz.read.should == '' + end + + it 'does not decrement pos' do + @gz.ungetc '' + @gz.pos.should == 10 + end + + it 'does not make eof? false' do + @gz.ungetc '' + @gz.eof?.should be_true + end + end + + quarantine! do # https://bugs.ruby-lang.org/issues/13675 + describe 'with nil' do + it 'does not append anything to the stream' do + @gz.ungetc nil + @gz.read.should == '' + end + + it 'does not decrement pos' do + @gz.ungetc nil + @gz.pos.should == 10 + end + + it 'does not make eof? false' do + @gz.ungetc nil + @gz.eof?.should be_true + end + end + end + end +end diff --git a/spec/ruby/library/zlib/gzipwriter/append_spec.rb b/spec/ruby/library/zlib/gzipwriter/append_spec.rb new file mode 100644 index 0000000000..6aa2824180 --- /dev/null +++ b/spec/ruby/library/zlib/gzipwriter/append_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipWriter#<<" do + before :each do + @io = StringIO.new + end + + it "returns self" do + Zlib::GzipWriter.wrap @io do |gzio| + (gzio << "test").should equal(gzio) + end + end +end diff --git a/spec/ruby/library/zlib/gzipwriter/mtime_spec.rb b/spec/ruby/library/zlib/gzipwriter/mtime_spec.rb new file mode 100644 index 0000000000..621b602dc7 --- /dev/null +++ b/spec/ruby/library/zlib/gzipwriter/mtime_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipWriter#mtime=" do + before :each do + @io = StringIO.new + end + + it "sets mtime using Integer" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.mtime = 1 + + gzio.mtime.should == Time.at(1) + end + + @io.string[4, 4].should == [1,0,0,0].pack('C*') + end + + it "sets mtime using Time" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.mtime = Time.at 1 + + gzio.mtime.should == Time.at(1) + end + + @io.string[4, 4].should == [1,0,0,0].pack('C*') + end + + it "raises if the header was written" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.write '' + + -> { gzio.mtime = nil }.should \ + raise_error(Zlib::GzipFile::Error, 'header is already written') + end + end +end diff --git a/spec/ruby/library/zlib/gzipwriter/write_spec.rb b/spec/ruby/library/zlib/gzipwriter/write_spec.rb new file mode 100644 index 0000000000..522ae7f2aa --- /dev/null +++ b/spec/ruby/library/zlib/gzipwriter/write_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require 'stringio' +require 'zlib' + +describe "Zlib::GzipWriter#write" do + before :each do + @data = '12345abcde' + @zip = [31, 139, 8, 0, 44, 220, 209, 71, 0, 3, 51, 52, 50, 54, 49, 77, + 76, 74, 78, 73, 5, 0, 157, 5, 0, 36, 10, 0, 0, 0].pack('C*') + @io = StringIO.new "".b + end + + it "writes some compressed data" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.write @data + end + + # skip gzip header for now + @io.string.unpack('C*')[10..-1].should == @zip.unpack('C*')[10..-1] + end + + it "returns the number of bytes in the input" do + Zlib::GzipWriter.wrap @io do |gzio| + gzio.write(@data).should == @data.size + end + end + + it "handles inputs of 2^23 bytes" do + input = '.'.b * (2 ** 23) + + Zlib::GzipWriter.wrap @io do |gzio| + gzio.write input + end + @io.string.size.should == 8176 + end +end diff --git a/spec/ruby/library/zlib/inflate/append_spec.rb b/spec/ruby/library/zlib/inflate/append_spec.rb new file mode 100644 index 0000000000..f121e66566 --- /dev/null +++ b/spec/ruby/library/zlib/inflate/append_spec.rb @@ -0,0 +1,60 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::Inflate#<<" do + before :all do + @foo_deflated = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') + end + + before :each do + @z = Zlib::Inflate.new + end + + after :each do + @z.close unless @z.closed? + end + + it "appends data to the input stream" do + @z << @foo_deflated + @z.finish.should == 'foo' + end + + it "treats nil argument as the end of compressed data" do + @z = Zlib::Inflate.new + @z << @foo_deflated << nil + @z.finish.should == 'foo' + end + + it "just passes through the data after nil argument" do + @z = Zlib::Inflate.new + @z << @foo_deflated << nil + @z << "-after_nil_data" + @z.finish.should == 'foo-after_nil_data' + end + + it "properly handles data in chunks" do + # add bytes, one by one + @foo_deflated.each_byte { |d| @z << d.chr} + @z.finish.should == "foo" + end + + it "properly handles incomplete data" do + # add bytes, one by one + @foo_deflated[0, 5].each_byte { |d| @z << d.chr} + -> { @z.finish }.should raise_error(Zlib::BufError) + end + + it "properly handles excessive data, byte-by-byte" do + # add bytes, one by one + data = @foo_deflated * 2 + data.each_byte { |d| @z << d.chr} + @z.finish.should == "foo" + @foo_deflated + end + + it "properly handles excessive data, in one go" do + # add bytes, one by one + data = @foo_deflated * 2 + @z << data + @z.finish.should == "foo" + @foo_deflated + end +end diff --git a/spec/ruby/library/zlib/inflate/finish_spec.rb b/spec/ruby/library/zlib/inflate/finish_spec.rb new file mode 100644 index 0000000000..3e0663e265 --- /dev/null +++ b/spec/ruby/library/zlib/inflate/finish_spec.rb @@ -0,0 +1,29 @@ +require_relative "../../../spec_helper" +require 'zlib' + +describe "Zlib::Inflate#finish" do + + before do + @zeros = Zlib::Deflate.deflate("0" * 100_000) + @inflator = Zlib::Inflate.new + @chunks = [] + + @inflator.inflate(@zeros) do |chunk| + @chunks << chunk + break + end + + @inflator.finish do |chunk| + @chunks << chunk + end + end + + it "inflates chunked data" do + @chunks.map { |chunk| chunk.length }.should == [16384, 16384, 16384, 16384, 16384, 16384, 1696] + end + + it "each chunk should have the same prefix" do + @chunks.all? { |chunk| chunk =~ /\A0+\z/ }.should be_true + end + +end diff --git a/spec/ruby/library/zlib/inflate/inflate_spec.rb b/spec/ruby/library/zlib/inflate/inflate_spec.rb new file mode 100644 index 0000000000..b308a4ba67 --- /dev/null +++ b/spec/ruby/library/zlib/inflate/inflate_spec.rb @@ -0,0 +1,159 @@ +require 'zlib' +require_relative '../../../spec_helper' + +describe "Zlib::Inflate#inflate" do + + before :each do + @inflator = Zlib::Inflate.new + end + it "inflates some data" do + data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*') + unzipped = @inflator.inflate data + @inflator.finish + + unzipped.should == "\000" * 10 + end + + it "inflates lots of data" do + data = [120, 156, 237, 193, 1, 1, 0, 0] + + [0, 128, 144, 254, 175, 238, 8, 10] + + Array.new(31, 0) + + [24, 128, 0, 0, 1] + + unzipped = @inflator.inflate data.pack('C*') + @inflator.finish + + unzipped.should == "\000" * 32 * 1024 + end + + it "works in pass-through mode, once finished" do + data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1] + @inflator.inflate data.pack('C*') + @inflator.finish # this is a precondition + + out = @inflator.inflate('uncompressed_data') + out << @inflator.finish + out.should == 'uncompressed_data' + + @inflator << ('uncompressed_data') << nil + @inflator.finish.should == 'uncompressed_data' + end + + it "has a binary encoding" do + data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1].pack('C*') + unzipped = @inflator.inflate data + @inflator.finish.encoding.should == Encoding::BINARY + unzipped.encoding.should == Encoding::BINARY + end + +end + +describe "Zlib::Inflate.inflate" do + + it "inflates some data" do + data = [120, 156, 99, 96, 128, 1, 0, 0, 10, 0, 1] + unzipped = Zlib::Inflate.inflate data.pack('C*') + + unzipped.should == "\000" * 10 + end + + it "inflates lots of data" do + data = [120, 156, 237, 193, 1, 1, 0, 0] + + [0, 128, 144, 254, 175, 238, 8, 10] + + Array.new(31,0) + + [24, 128, 0, 0, 1] + + zipped = Zlib::Inflate.inflate data.pack('C*') + + zipped.should == "\000" * 32 * 1024 + end + + it "properly handles data in chunks" do + data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') + z = Zlib::Inflate.new + # add bytes, one by one + result = +"" + data.each_byte { |d| result << z.inflate(d.chr)} + result << z.finish + result.should == "foo" + end + + it "properly handles incomplete data" do + data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')[0,5] + z = Zlib::Inflate.new + # add bytes, one by one, but not all + result = +"" + data.each_byte { |d| result << z.inflate(d.chr)} + -> { result << z.finish }.should raise_error(Zlib::BufError) + end + + it "properly handles excessive data, byte-by-byte" do + main_data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') + data = main_data * 2 + result = +"" + + z = Zlib::Inflate.new + # add bytes, one by one + data.each_byte { |d| result << z.inflate(d.chr)} + result << z.finish + + # the first chunk is inflated to its completion, + # the second chunk is just passed through. + result.should == "foo" + main_data + end + + it "properly handles excessive data, in one go" do + main_data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') + data = main_data * 2 + result = +"" + + z = Zlib::Inflate.new + result << z.inflate(data) + result << z.finish + + # the first chunk is inflated to its completion, + # the second chunk is just passed through. + result.should == "foo" + main_data + end +end + +describe "Zlib::Inflate#inflate" do + + before do + @zeros = Zlib::Deflate.deflate("0" * 100_000) + @inflator = Zlib::Inflate.new + @chunks = [] + end + + describe "without break" do + + before do + @inflator.inflate(@zeros) do |chunk| + @chunks << chunk + end + end + + it "inflates chunked data" do + @chunks.map { |chunk| chunk.size }.should == [16384, 16384, 16384, 16384, 16384, 16384, 1696] + end + + it "properly handles chunked data" do + @chunks.all? { |chunk| chunk =~ /\A0+\z/ }.should be_true + end + end + + describe "with break" do + + before do + @inflator.inflate(@zeros) do |chunk| + @chunks << chunk + break + end + end + + it "inflates chunked break" do + output = @inflator.inflate nil + (100_000 - @chunks.first.length).should == output.length + end + end +end diff --git a/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb b/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb new file mode 100644 index 0000000000..375ee3c765 --- /dev/null +++ b/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb @@ -0,0 +1,20 @@ +# encoding: binary +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::Inflate#set_dictionary" do + it "sets the inflate dictionary" do + deflated = "x\273\024\341\003\313KLJNIMK\317\310\314\002\000\025\206\003\370" + + i = Zlib::Inflate.new + + begin + i << deflated + flunk 'Zlib::NeedDict not raised' + rescue Zlib::NeedDict + i.set_dictionary 'aaaaaaaaaa' + end + + i.finish.should == 'abcdefghij' + end +end diff --git a/spec/ruby/library/zlib/inflate_spec.rb b/spec/ruby/library/zlib/inflate_spec.rb new file mode 100644 index 0000000000..42c8dc5fbe --- /dev/null +++ b/spec/ruby/library/zlib/inflate_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require "zlib" + +describe "Zlib.inflate" do + it "inflates some data" do + Zlib.inflate([120, 156, 51, 52, 132, 1, 0, 10, 145, 1, 235].pack('C*')).should == "1" * 10 + end +end diff --git a/spec/ruby/library/zlib/zlib_version_spec.rb b/spec/ruby/library/zlib/zlib_version_spec.rb new file mode 100644 index 0000000000..f83dfae66d --- /dev/null +++ b/spec/ruby/library/zlib/zlib_version_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require 'zlib' + +describe "Zlib.zlib_version" do + it "returns the version of the libz library" do + Zlib.zlib_version.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/library/zlib/zstream/adler_spec.rb b/spec/ruby/library/zlib/zstream/adler_spec.rb new file mode 100644 index 0000000000..55ac8ae79e --- /dev/null +++ b/spec/ruby/library/zlib/zstream/adler_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::ZStream#adler" do + it "generates hash" do + z = Zlib::Deflate.new + z << "foo" + z.finish + z.adler.should == 0x02820145 + end +end diff --git a/spec/ruby/library/zlib/zstream/avail_in_spec.rb b/spec/ruby/library/zlib/zstream/avail_in_spec.rb new file mode 100644 index 0000000000..eddae15830 --- /dev/null +++ b/spec/ruby/library/zlib/zstream/avail_in_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::ZStream#avail_in" do + it "returns bytes in the input buffer" do + z = Zlib::Deflate.new + z.avail_in.should == 0 + end +end diff --git a/spec/ruby/library/zlib/zstream/avail_out_spec.rb b/spec/ruby/library/zlib/zstream/avail_out_spec.rb new file mode 100644 index 0000000000..2e5a394ec0 --- /dev/null +++ b/spec/ruby/library/zlib/zstream/avail_out_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::ZStream#avail_out" do + it "returns bytes in the output buffer" do + z = Zlib::Deflate.new + z.avail_out.should == 0 + end +end diff --git a/spec/ruby/library/zlib/zstream/data_type_spec.rb b/spec/ruby/library/zlib/zstream/data_type_spec.rb new file mode 100644 index 0000000000..8be96adf7c --- /dev/null +++ b/spec/ruby/library/zlib/zstream/data_type_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::ZStream#data_type" do + it "returns the type of the data in the stream" do + z = Zlib::Deflate.new + [Zlib::ASCII, Zlib::BINARY, Zlib::UNKNOWN].include?(z.data_type).should == true + end +end diff --git a/spec/ruby/library/zlib/zstream/flush_next_out_spec.rb b/spec/ruby/library/zlib/zstream/flush_next_out_spec.rb new file mode 100644 index 0000000000..63676a8203 --- /dev/null +++ b/spec/ruby/library/zlib/zstream/flush_next_out_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require 'zlib' + +describe "Zlib::ZStream#flush_next_out" do + + it "flushes the stream and flushes the output buffer" do + zs = Zlib::Inflate.new + zs << [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') + + zs.flush_next_out.should == 'foo' + zs.should.finished? + zs.flush_next_out.should == '' + end +end |
