diff options
Diffstat (limited to 'spec/ruby/core/kernel')
-rw-r--r-- | spec/ruby/core/kernel/open_spec.rb | 5 | ||||
-rw-r--r-- | spec/ruby/core/kernel/printf_spec.rb | 27 | ||||
-rw-r--r-- | spec/ruby/core/kernel/shared/sprintf.rb | 871 | ||||
-rw-r--r-- | spec/ruby/core/kernel/shared/sprintf_encoding.rb | 28 | ||||
-rw-r--r-- | spec/ruby/core/kernel/sprintf_spec.rb | 316 |
5 files changed, 944 insertions, 303 deletions
diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index 4e99061219..ff56dfa03b 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -33,13 +33,14 @@ describe "Kernel#open" do @io = open("|date") begin @io.should be_kind_of(IO) + @io.read ensure @io.close end end it "opens an io when called with a block" do - @output = open("|date") { |f| f.gets } + @output = open("|date") { |f| f.read } @output.should_not == '' end @@ -61,7 +62,7 @@ describe "Kernel#open" do end it "opens an io when called with a block" do - @output = open("|date /t") { |f| f.gets } + @output = open("|date /t") { |f| f.read } @output.should_not == '' end end diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb index b4c68fa449..ed134027c0 100644 --- a/spec/ruby/core/kernel/printf_spec.rb +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -1,5 +1,7 @@ require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../fixtures/classes', __FILE__) +require File.expand_path('../shared/sprintf', __FILE__) +require "stringio" describe "Kernel#printf" do it "is a private method" do @@ -31,4 +33,29 @@ describe "Kernel.printf" do object.should_receive(:write).with("string") Kernel.printf(object, "%s", "string") end + + describe "formatting" do + context "io is specified" do + it_behaves_like :kernel_sprintf, -> (format, *args) { + io = StringIO.new + printf(io, format, *args) + io.string + } + end + + context "io is not specified" do + it_behaves_like :kernel_sprintf, -> (format, *args) { + stdout = $stdout + + begin + $stdout = io = StringIO.new + printf(format, *args) + io.string + ensure + $stdout = stdout + end + } + end + end end + diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb new file mode 100644 index 0000000000..e595a06640 --- /dev/null +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -0,0 +1,871 @@ +describe :kernel_sprintf, shared: true do + def format(*args) + @method.call(*args) + end + + describe "integer formats" do + it "converts argument into Integer with to_int" do + obj = Object.new + def obj.to_i; 10; end + def obj.to_int; 10; end + + obj.should_receive(:to_int).and_return(10) + format("%b", obj).should == "1010" + end + + it "converts argument into Integer with to_i if to_int isn't available" do + obj = Object.new + def obj.to_i; 10; end + + obj.should_receive(:to_i).and_return(10) + format("%b", obj).should == "1010" + end + + it "converts String argument with Kernel#Integer" do + format("%d", "0b1010").should == "10" + format("%d", "112").should == "112" + format("%d", "0127").should == "87" + format("%d", "0xc4").should == "196" + end + + it "raises TypeError exception if cannot convert to Integer" do + -> () { + format("%b", Object.new) + }.should raise_error(TypeError) + end + + ["b", "B"].each do |f| + describe f do + it "converts argument as a binary number" do + format("%#{f}", 10).should == "1010" + end + + it "displays negative number as a two's complement prefixed with '..1'" do + format("%#{f}", -10).should == "..1" + "0110" + end + + it "collapse negative number representation if it equals 1" do + format("%#{f}", -1).should_not == "..11" + format("%#{f}", -1).should == "..1" + end + end + end + + ["d", "i", "u"].each do |f| + describe f do + it "converts argument as a decimal number" do + format("%#{f}", 112).should == "112" + format("%#{f}", -112).should == "-112" + end + + it "works well with large numbers" do + format("%#{f}", 1234567890987654321).should == "1234567890987654321" + end + end + end + + describe "o" do + it "converts argument as an octal number" do + format("%o", 87).should == "127" + end + + it "displays negative number as a two's complement prefixed with '..7'" do + format("%o", -87).should == "..7" + "651" + end + + it "collapse negative number representation if it equals 7" do + format("%o", -1).should_not == "..77" + format("%o", -1).should == "..7" + end + end + + describe "x" do + it "converts argument as a hexadecimal number" do + format("%x", 196).should == "c4" + end + + it "displays negative number as a two's complement prefixed with '..f'" do + format("%x", -196).should == "..f" + "3c" + end + + it "collapse negative number representation if it equals f" do + format("%x", -1).should_not == "..ff" + format("%x", -1).should == "..f" + end + end + + describe "X" do + it "converts argument as a hexadecimal number with uppercase letters" do + format("%X", 196).should == "C4" + end + + it "displays negative number as a two's complement prefixed with '..f'" do + format("%X", -196).should == "..F" + "3C" + end + + it "collapse negative number representation if it equals F" do + format("%X", -1).should_not == "..FF" + format("%X", -1).should == "..F" + end + end + end + + describe "float formats" do + it "converts argument into Float" do + obj = mock("float") + obj.should_receive(:to_f).and_return(9.6) + format("%f", obj).should == "9.600000" + end + + it "raises TypeError exception if cannot convert to Float" do + -> () { + format("%f", Object.new) + }.should raise_error(TypeError) + end + + {"e" => "e", "E" => "E"}.each_pair do |f, exp| + describe f do + it "converts argument into exponential notation [-]d.dddddde[+-]dd" do + format("%#{f}", 109.52).should == "1.095200#{exp}+02" + format("%#{f}", -109.52).should == "-1.095200#{exp}+02" + format("%#{f}", 0.10952).should == "1.095200#{exp}-01" + format("%#{f}", -0.10952).should == "-1.095200#{exp}-01" + end + + it "cuts excessive digits and keeps only 6 ones" do + format("%#{f}", 1.123456789).should == "1.123457#{exp}+00" + end + + it "rounds the last significant digit to the closest one" do + format("%#{f}", 1.555555555).should == "1.555556#{exp}+00" + format("%#{f}", -1.555555555).should == "-1.555556#{exp}+00" + format("%#{f}", 1.444444444).should == "1.444444#{exp}+00" + end + + it "displays Float::INFINITY as Inf" do + format("%#{f}", Float::INFINITY).should == "Inf" + format("%#{f}", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + format("%#{f}", Float::NAN).should == "NaN" + format("%#{f}", -Float::NAN).should == "NaN" + end + end + end + + describe "f" do + it "converts floating point argument as [-]ddd.dddddd" do + format("%f", 10.952).should == "10.952000" + format("%f", -10.952).should == "-10.952000" + end + + it "cuts excessive digits and keeps only 6 ones" do + format("%f", 1.123456789).should == "1.123457" + end + + it "rounds the last significant digit to the closest one" do + format("%f", 1.555555555).should == "1.555556" + format("%f", -1.555555555).should == "-1.555556" + format("%f", 1.444444444).should == "1.444444" + end + + it "displays Float::INFINITY as Inf" do + format("%f", Float::INFINITY).should == "Inf" + format("%f", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + format("%f", Float::NAN).should == "NaN" + format("%f", -Float::NAN).should == "NaN" + end + end + + {"g" => "e", "G" => "E"}.each_pair do |f, exp| + describe f do + context "the exponent is less than -4" do + it "converts a floating point number using exponential form" do + format("%#{f}", 0.0000123456).should == "1.23456#{exp}-05" + format("%#{f}", -0.0000123456).should == "-1.23456#{exp}-05" + + format("%#{f}", 0.000000000123456).should == "1.23456#{exp}-10" + format("%#{f}", -0.000000000123456).should == "-1.23456#{exp}-10" + end + end + + context "the exponent is greater than or equal to the precision (6 by default)" do + it "converts a floating point number using exponential form" do + format("%#{f}", 1234567).should == "1.23457#{exp}+06" + format("%#{f}", 1234567890123).should == "1.23457#{exp}+12" + format("%#{f}", -1234567).should == "-1.23457#{exp}+06" + end + end + + context "otherwise" do + it "converts a floating point number in dd.dddd form" do + format("%#{f}", 0.0001).should == "0.0001" + format("%#{f}", -0.0001).should == "-0.0001" + format("%#{f}", 123456).should == "123456" + format("%#{f}", -123456).should == "-123456" + end + + it "cuts excessive digits in fractional part and keeps only 4 ones" do + format("%#{f}", 12.12341111).should == "12.1234" + format("%#{f}", -12.12341111).should == "-12.1234" + end + + it "rounds the last significant digit to the closest one in fractional part" do + format("%#{f}", 1.555555555).should == "1.55556" + format("%#{f}", -1.555555555).should == "-1.55556" + format("%#{f}", 1.444444444).should == "1.44444" + end + + it "cuts fraction part to have only 6 digits at all" do + format("%#{f}", 1.1234567).should == "1.12346" + format("%#{f}", 12.1234567).should == "12.1235" + format("%#{f}", 123.1234567).should == "123.123" + format("%#{f}", 1234.1234567).should == "1234.12" + format("%#{f}", 12345.1234567).should == "12345.1" + format("%#{f}", 123456.1234567).should == "123456" + end + end + + it "displays Float::INFINITY as Inf" do + format("%#{f}", Float::INFINITY).should == "Inf" + format("%#{f}", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + format("%#{f}", Float::NAN).should == "NaN" + format("%#{f}", -Float::NAN).should == "NaN" + end + end + end + + describe "a" do + it "converts floating point argument as [-]0xh.hhhhp[+-]dd" do + format("%a", 196).should == "0x1.88p+7" + format("%a", -196).should == "-0x1.88p+7" + format("%a", 196.1).should == "0x1.8833333333333p+7" + format("%a", 0.01).should == "0x1.47ae147ae147bp-7" + format("%a", -0.01).should == "-0x1.47ae147ae147bp-7" + end + + it "displays Float::INFINITY as Inf" do + format("%a", Float::INFINITY).should == "Inf" + format("%a", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + format("%a", Float::NAN).should == "NaN" + format("%a", -Float::NAN).should == "NaN" + end + end + + describe "A" do + it "converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P" do + format("%A", 196).should == "0X1.88P+7" + format("%A", -196).should == "-0X1.88P+7" + format("%A", 196.1).should == "0X1.8833333333333P+7" + format("%A", 0.01).should == "0X1.47AE147AE147BP-7" + format("%A", -0.01).should == "-0X1.47AE147AE147BP-7" + end + + it "displays Float::INFINITY as Inf" do + format("%A", Float::INFINITY).should == "Inf" + format("%A", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + format("%A", Float::NAN).should == "NaN" + format("%A", -Float::NAN).should == "NaN" + end + end + end + + describe "other formats" do + describe "c" do + it "displays character if argument is a numeric code of character" do + format("%c", 97).should == "a" + end + + it "displays character if argument is a single character string" do + format("%c", "a").should == "a" + end + + it "raises ArgumentError if argument is a string of several characters" do + -> () { + format("%c", "abc") + }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if argument is an empty string" do + -> () { + format("%c", "") + }.should raise_error(ArgumentError) + end + + it "supports Unicode characters" do + format("%c", 1286).should == "Ԇ" + format("%c", "ش").should == "ش" + end + end + + describe "p" do + it "displays argument.inspect value" do + obj = mock("object") + obj.should_receive(:inspect).and_return("<inspect-result>") + format("%p", obj).should == "<inspect-result>" + end + end + + describe "s" do + it "substitute argument passes as a string" do + format("%s", "abc").should == "abc" + end + + it "converts argument to string with to_s" do + obj = mock("string") + obj.should_receive(:to_s).and_return("abc") + format("%s", obj).should == "abc" + end + + it "does not try to convert with to_str" do + obj = BasicObject.new + def obj.to_str + "abc" + end + + -> () { + format("%s", obj) + }.should raise_error(NoMethodError) + end + end + + describe "%" do + ruby_version_is ""..."2.5" do + it "alone displays the percent sign" do + format("%").should == "%" + end + end + + ruby_version_is "2.5" do + it "alone raises an ArgumentError" do + -> { + format("%") + }.should raise_error(ArgumentError) + end + end + + it "is escaped by %" do + format("%%").should == "%" + format("%%d", 10).should == "%d" + end + end + end + + describe "flags" do + describe "space" do + context "applies to numeric formats bBdiouxXeEfgGaA" do + it "leaves a space at the start of non-negative numbers" do + format("% b", 10).should == " 1010" + format("% B", 10).should == " 1010" + format("% d", 112).should == " 112" + format("% i", 112).should == " 112" + format("% o", 87).should == " 127" + format("% u", 112).should == " 112" + format("% x", 196).should == " c4" + format("% X", 196).should == " C4" + + format("% e", 109.52).should == " 1.095200e+02" + format("% E", 109.52).should == " 1.095200E+02" + format("% f", 10.952).should == " 10.952000" + format("% g", 12.1234).should == " 12.1234" + format("% G", 12.1234).should == " 12.1234" + format("% a", 196).should == " 0x1.88p+7" + format("% A", 196).should == " 0X1.88P+7" + end + + it "does not leave a space at the start of negative numbers" do + format("% b", -10).should == "-1010" + format("% B", -10).should == "-1010" + format("% d", -112).should == "-112" + format("% i", -112).should == "-112" + format("% o", -87).should == "-127" + format("% u", -112).should == "-112" + format("% x", -196).should == "-c4" + format("% X", -196).should == "-C4" + + format("% e", -109.52).should == "-1.095200e+02" + format("% E", -109.52).should == "-1.095200E+02" + format("% f", -10.952).should == "-10.952000" + format("% g", -12.1234).should == "-12.1234" + format("% G", -12.1234).should == "-12.1234" + format("% a", -196).should == "-0x1.88p+7" + format("% A", -196).should == "-0X1.88P+7" + end + + it "prevents converting negative argument to two's complement form" do + format("% b", -10).should == "-1010" + format("% B", -10).should == "-1010" + format("% o", -87).should == "-127" + format("% x", -196).should == "-c4" + format("% X", -196).should == "-C4" + end + + it "treats several white spaces as one" do + format("% b", 10).should == " 1010" + format("% B", 10).should == " 1010" + format("% d", 112).should == " 112" + format("% i", 112).should == " 112" + format("% o", 87).should == " 127" + format("% u", 112).should == " 112" + format("% x", 196).should == " c4" + format("% X", 196).should == " C4" + + format("% e", 109.52).should == " 1.095200e+02" + format("% E", 109.52).should == " 1.095200E+02" + format("% f", 10.952).should == " 10.952000" + format("% g", 12.1234).should == " 12.1234" + format("% G", 12.1234).should == " 12.1234" + format("% a", 196).should == " 0x1.88p+7" + format("% A", 196).should == " 0X1.88P+7" + end + end + end + + describe "(digit)$" do + it "specifies the absolute argument number for this field" do + format("%2$b", 0, 10).should == "1010" + format("%2$B", 0, 10).should == "1010" + format("%2$d", 0, 112).should == "112" + format("%2$i", 0, 112).should == "112" + format("%2$o", 0, 87).should == "127" + format("%2$u", 0, 112).should == "112" + format("%2$x", 0, 196).should == "c4" + format("%2$X", 0, 196).should == "C4" + + format("%2$e", 0, 109.52).should == "1.095200e+02" + format("%2$E", 0, 109.52).should == "1.095200E+02" + format("%2$f", 0, 10.952).should == "10.952000" + format("%2$g", 0, 12.1234).should == "12.1234" + format("%2$G", 0, 12.1234).should == "12.1234" + format("%2$a", 0, 196).should == "0x1.88p+7" + format("%2$A", 0, 196).should == "0X1.88P+7" + + format("%2$c", 1, 97).should == "a" + format("%2$p", "a", []).should == "[]" + format("%2$s", "-", "abc").should == "abc" + end + + it "raises exception if argument number is bigger than actual arguments list" do + -> () { + format("%4$d", 1, 2, 3) + }.should raise_error(ArgumentError) + end + + it "ignores '-' sign" do + format("%2$d", 1, 2, 3).should == "2" + format("%-2$d", 1, 2, 3).should == "2" + end + + it "raises ArgumentError exception when absolute and relative argument numbers are mixed" do + -> () { + format("%1$d %d", 1, 2) + }.should raise_error(ArgumentError) + end + end + + describe "#" do + context "applies to format o" do + it "increases the precision until the first digit will be `0' if it is not formatted as complements" do + format("%#o", 87).should == "0127" + end + + it "does nothing for negative argument" do + format("%#o", -87).should == "..7651" + end + end + + context "applies to formats bBxX" do + it "prefixes the result with 0x, 0X, 0b and 0B respectively for non-zero argument" do + format("%#b", 10).should == "0b1010" + format("%#b", -10).should == "0b..10110" + format("%#B", 10).should == "0B1010" + format("%#B", -10).should == "0B..10110" + + format("%#x", 196).should == "0xc4" + format("%#x", -196).should == "0x..f3c" + format("%#X", 196).should == "0XC4" + format("%#X", -196).should == "0X..F3C" + end + + it "does nothing for zero argument" do + format("%#b", 0).should == "0" + format("%#B", 0).should == "0" + + format("%#o", 0).should == "0" + + format("%#x", 0).should == "0" + format("%#X", 0).should == "0" + end + end + + context "applies to formats aAeEfgG" do + it "forces a decimal point to be added, even if no digits follow" do + format("%#.0a", 16.25).should == "0x1.p+4" + format("%#.0A", 16.25).should == "0X1.P+4" + + format("%#.0e", 100).should == "1.e+02" + format("%#.0E", 100).should == "1.E+02" + + format("%#.0f", 123.4).should == "123." + + format("%#g", 123456).should == "123456." + format("%#G", 123456).should == "123456." + end + + it "changes format from dd.dddd to exponential form for gG" do + format("%#.0g", 123.4).should_not == "123." + format("%#.0g", 123.4).should == "1.e+02" + end + end + + context "applies to gG" do + it "does not remove trailing zeros" do + format("%#g", 123.4).should == "123.400" + format("%#g", 123.4).should == "123.400" + end + end + end + + describe "+" do + context "applies to numeric formats bBdiouxXaAeEfgG" do + it "adds a leading plus sign to non-negative numbers" do + format("%+b", 10).should == "+1010" + format("%+B", 10).should == "+1010" + format("%+d", 112).should == "+112" + format("%+i", 112).should == "+112" + format("%+o", 87).should == "+127" + format("%+u", 112).should == "+112" + format("%+x", 196).should == "+c4" + format("%+X", 196).should == "+C4" + + format("%+e", 109.52).should == "+1.095200e+02" + format("%+E", 109.52).should == "+1.095200E+02" + format("%+f", 10.952).should == "+10.952000" + format("%+g", 12.1234).should == "+12.1234" + format("%+G", 12.1234).should == "+12.1234" + format("%+a", 196).should == "+0x1.88p+7" + format("%+A", 196).should == "+0X1.88P+7" + end + + it "does not use two's complement form for negative numbers for formats bBoxX" do + format("%+b", -10).should == "-1010" + format("%+B", -10).should == "-1010" + format("%+o", -87).should == "-127" + format("%+x", -196).should == "-c4" + format("%+X", -196).should == "-C4" + end + end + end + + describe "-" do + it "left-justifies the result of conversion if width is specified" do + format("%-10b", 10).should == "1010 " + format("%-10B", 10).should == "1010 " + format("%-10d", 112).should == "112 " + format("%-10i", 112).should == "112 " + format("%-10o", 87).should == "127 " + format("%-10u", 112).should == "112 " + format("%-10x", 196).should == "c4 " + format("%-10X", 196).should == "C4 " + + format("%-20e", 109.52).should == "1.095200e+02 " + format("%-20E", 109.52).should == "1.095200E+02 " + format("%-20f", 10.952).should == "10.952000 " + format("%-20g", 12.1234).should == "12.1234 " + format("%-20G", 12.1234).should == "12.1234 " + format("%-20a", 196).should == "0x1.88p+7 " + format("%-20A", 196).should == "0X1.88P+7 " + + format("%-10c", 97).should == "a " + format("%-10p", []).should == "[] " + format("%-10s", "abc").should == "abc " + end + end + + describe "0 (zero)" do + context "applies to numeric formats bBdiouxXaAeEfgG and width is specified" do + it "pads with zeros, not spaces" do + format("%010b", 10).should == "0000001010" + format("%010B", 10).should == "0000001010" + format("%010d", 112).should == "0000000112" + format("%010i", 112).should == "0000000112" + format("%010o", 87).should == "0000000127" + format("%010u", 112).should == "0000000112" + format("%010x", 196).should == "00000000c4" + format("%010X", 196).should == "00000000C4" + + format("%020e", 109.52).should == "000000001.095200e+02" + format("%020E", 109.52).should == "000000001.095200E+02" + format("%020f", 10.952).should == "0000000000010.952000" + format("%020g", 12.1234).should == "000000000000012.1234" + format("%020G", 12.1234).should == "000000000000012.1234" + format("%020a", 196).should == "0x000000000001.88p+7" + format("%020A", 196).should == "0X000000000001.88P+7" + end + + it "uses radix-1 when displays negative argument as a two's complement" do + format("%010b", -10).should == "..11110110" + format("%010B", -10).should == "..11110110" + format("%010o", -87).should == "..77777651" + format("%010x", -196).should == "..ffffff3c" + format("%010X", -196).should == "..FFFFFF3C" + end + end + end + + describe "*" do + it "uses the previous argument as the field width" do + format("%*b", 10, 10).should == " 1010" + format("%*B", 10, 10).should == " 1010" + format("%*d", 10, 112).should == " 112" + format("%*i", 10, 112).should == " 112" + format("%*o", 10, 87).should == " 127" + format("%*u", 10, 112).should == " 112" + format("%*x", 10, 196).should == " c4" + format("%*X", 10, 196).should == " C4" + + format("%*e", 20, 109.52).should == " 1.095200e+02" + format("%*E", 20, 109.52).should == " 1.095200E+02" + format("%*f", 20, 10.952).should == " 10.952000" + format("%*g", 20, 12.1234).should == " 12.1234" + format("%*G", 20, 12.1234).should == " 12.1234" + format("%*a", 20, 196).should == " 0x1.88p+7" + format("%*A", 20, 196).should == " 0X1.88P+7" + + format("%*c", 10, 97).should == " a" + format("%*p", 10, []).should == " []" + format("%*s", 10, "abc").should == " abc" + end + + it "left-justifies the result if width is negative" do + format("%*b", -10, 10).should == "1010 " + format("%*B", -10, 10).should == "1010 " + format("%*d", -10, 112).should == "112 " + format("%*i", -10, 112).should == "112 " + format("%*o", -10, 87).should == "127 " + format("%*u", -10, 112).should == "112 " + format("%*x", -10, 196).should == "c4 " + format("%*X", -10, 196).should == "C4 " + + format("%*e", -20, 109.52).should == "1.095200e+02 " + format("%*E", -20, 109.52).should == "1.095200E+02 " + format("%*f", -20, 10.952).should == "10.952000 " + format("%*g", -20, 12.1234).should == "12.1234 " + format("%*G", -20, 12.1234).should == "12.1234 " + format("%*a", -20, 196).should == "0x1.88p+7 " + format("%*A", -20, 196).should == "0X1.88P+7 " + + format("%*c", -10, 97).should == "a " + format("%*p", -10, []).should == "[] " + format("%*s", -10, "abc").should == "abc " + end + + it "uses the specified argument as the width if * is followed by a number and $" do + format("%1$*2$b", 10, 10).should == " 1010" + format("%1$*2$B", 10, 10).should == " 1010" + format("%1$*2$d", 112, 10).should == " 112" + format("%1$*2$i", 112, 10).should == " 112" + format("%1$*2$o", 87, 10).should == " 127" + format("%1$*2$u", 112, 10).should == " 112" + format("%1$*2$x", 196, 10).should == " c4" + format("%1$*2$X", 196, 10).should == " C4" + + format("%1$*2$e", 109.52, 20).should == " 1.095200e+02" + format("%1$*2$E", 109.52, 20).should == " 1.095200E+02" + format("%1$*2$f", 10.952, 20).should == " 10.952000" + format("%1$*2$g", 12.1234, 20).should == " 12.1234" + format("%1$*2$G", 12.1234, 20).should == " 12.1234" + format("%1$*2$a", 196, 20).should == " 0x1.88p+7" + format("%1$*2$A", 196, 20).should == " 0X1.88P+7" + + format("%1$*2$c", 97, 10).should == " a" + format("%1$*2$p", [], 10).should == " []" + format("%1$*2$s", "abc", 10).should == " abc" + end + + it "left-justifies the result if specified with $ argument is negative" do + format("%1$*2$b", 10, -10).should == "1010 " + format("%1$*2$B", 10, -10).should == "1010 " + format("%1$*2$d", 112, -10).should == "112 " + format("%1$*2$i", 112, -10).should == "112 " + format("%1$*2$o", 87, -10).should == "127 " + format("%1$*2$u", 112, -10).should == "112 " + format("%1$*2$x", 196, -10).should == "c4 " + format("%1$*2$X", 196, -10).should == "C4 " + + format("%1$*2$e", 109.52, -20).should == "1.095200e+02 " + format("%1$*2$E", 109.52, -20).should == "1.095200E+02 " + format("%1$*2$f", 10.952, -20).should == "10.952000 " + format("%1$*2$g", 12.1234, -20).should == "12.1234 " + format("%1$*2$G", 12.1234, -20).should == "12.1234 " + format("%1$*2$a", 196, -20).should == "0x1.88p+7 " + format("%1$*2$A", 196, -20).should == "0X1.88P+7 " + + format("%1$*2$c", 97, -10).should == "a " + format("%1$*2$p", [], -10).should == "[] " + format("%1$*2$s", "abc", -10).should == "abc " + end + + it "raises ArgumentError when is mixed with width" do + -> () { + format("%*10d", 10, 112) + }.should raise_error(ArgumentError) + end + end + end + + describe "width" do + it "specifies the minimum number of characters that will be written to the result" do + format("%10b", 10).should == " 1010" + format("%10B", 10).should == " 1010" + format("%10d", 112).should == " 112" + format("%10i", 112).should == " 112" + format("%10o", 87).should == " 127" + format("%10u", 112).should == " 112" + format("%10x", 196).should == " c4" + format("%10X", 196).should == " C4" + + format("%20e", 109.52).should == " 1.095200e+02" + format("%20E", 109.52).should == " 1.095200E+02" + format("%20f", 10.952).should == " 10.952000" + format("%20g", 12.1234).should == " 12.1234" + format("%20G", 12.1234).should == " 12.1234" + format("%20a", 196).should == " 0x1.88p+7" + format("%20A", 196).should == " 0X1.88P+7" + + format("%10c", 97).should == " a" + format("%10p", []).should == " []" + format("%10s", "abc").should == " abc" + end + + it "is ignored if argument's actual length is greater" do + format("%5d", 1234567890).should == "1234567890" + end + end + + describe "precision" do + context "integer types" do + it "controls the number of decimal places displayed" do + format("%.6b", 10).should == "001010" + format("%.6B", 10).should == "001010" + format("%.5d", 112).should == "00112" + format("%.5i", 112).should == "00112" + format("%.5o", 87).should == "00127" + format("%.5u", 112).should == "00112" + + format("%.5x", 196).should == "000c4" + format("%.5X", 196).should == "000C4" + end + end + + context "float types" do + it "controls the number of decimal places displayed in fraction part" do + format("%.10e", 109.52).should == "1.0952000000e+02" + format("%.10E", 109.52).should == "1.0952000000E+02" + format("%.10f", 10.952).should == "10.9520000000" + format("%.10a", 196).should == "0x1.8800000000p+7" + format("%.10A", 196).should == "0X1.8800000000P+7" + end + + it "does not affect G format" do + format("%.10g", 12.1234).should == "12.1234" + format("%.10g", 123456789).should == "123456789" + end + end + + context "string formats" do + it "determines the maximum number of characters to be copied from the string" do + format("%.1p", [1]).should == "[" + format("%.2p", [1]).should == "[1" + format("%.10p", [1]).should == "[1]" + format("%.0p", [1]).should == "" + + format("%.1s", "abc").should == "a" + format("%.2s", "abc").should == "ab" + format("%.10s", "abc").should == "abc" + format("%.0s", "abc").should == "" + end + end + end + + describe "reference by name" do + describe "%<name>s style" do + it "uses value passed in a hash argument" do + format("%<foo>d", foo: 123).should == "123" + end + + it "supports flags, width, precision and type" do + format("%+20.10<foo>f", foo: 10.952).should == " +10.9520000000" + end + + it "allows to place name in any position" do + format("%+15.5<foo>f", foo: 10.952).should == " +10.95200" + format("%+15<foo>.5f", foo: 10.952).should == " +10.95200" + format("%+<foo>15.5f", foo: 10.952).should == " +10.95200" + format("%<foo>+15.5f", foo: 10.952).should == " +10.95200" + end + + it "cannot be mixed with unnamed style" do + -> () { + format("%d %<foo>d", 1, foo: "123") + }.should raise_error(ArgumentError) + end + + it "raises KeyError when there is no matching key" do + -> () { + format("%<foo>s", {}) + }.should raise_error(KeyError) + end + end + + describe "%{name} style" do + it "uses value passed in a hash argument" do + format("%{foo}", foo: 123).should == "123" + end + + it "does not support type style" do + format("%{foo}d", foo: 123).should == "123d" + end + + it "supports flags, width and precision" do + format("%-20.5{foo}", foo: "123456789").should == "12345 " + end + + it "cannot be mixed with unnamed style" do + -> () { + format("%d %{foo}", 1, foo: "123") + }.should raise_error(ArgumentError) + end + + it "raises KeyError when there is no matching key" do + -> () { + format("%{foo}", {}) + }.should raise_error(KeyError) + end + + it "converts value to String with to_s" do + obj = Object.new + def obj.to_s; end + def obj.to_str; end + + obj.should_receive(:to_s).and_return("42") + obj.should_not_receive(:to_str) + + format("%{foo}", foo: obj).should == "42" + end + end + end +end diff --git a/spec/ruby/core/kernel/shared/sprintf_encoding.rb b/spec/ruby/core/kernel/shared/sprintf_encoding.rb new file mode 100644 index 0000000000..a92f3c10cd --- /dev/null +++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb @@ -0,0 +1,28 @@ +describe :kernel_sprintf_encoding, shared: true do + def format(*args) + @method.call(*args) + end + + it "returns a String in the same encoding as the format String if compatible" do + string = "%s".force_encoding(Encoding::KOI8_U) + result = format(string, "dogs") + result.encoding.should equal(Encoding::KOI8_U) + end + + it "returns a String in the argument's encoding if format encoding is more restrictive" do + string = "foo %s".force_encoding(Encoding::US_ASCII) + argument = "b\303\274r".force_encoding(Encoding::UTF_8) + + result = format(string, argument) + result.encoding.should equal(Encoding::UTF_8) + end + + it "raises Encoding::CompatibilityError if both encodings are ASCII compatible and there ano not ASCII characters" do + string = "Ä %s".encode('windows-1252') + argument = "Ђ".encode('windows-1251') + + -> () { + format(string, argument) + }.should raise_error(Encoding::CompatibilityError) + end +end diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index 984f31dc7f..a89b253803 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -1,310 +1,24 @@ require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../fixtures/classes', __FILE__) +require File.expand_path('../shared/sprintf', __FILE__) +require File.expand_path('../shared/sprintf_encoding', __FILE__) describe "Kernel#sprintf" do - it "is a private method" do - Kernel.should have_private_instance_method(:sprintf) - end + it_behaves_like :kernel_sprintf, -> (format, *args) { + sprintf(format, *args) + } - it "treats nil arguments as zero-width strings in %s slots" do - sprintf("%s%d%s%s", nil, 4, 'a', 'b').should == '4ab' - end - - it "passes some tests for positive %x" do - sprintf("%x", 123).should == "7b" - sprintf("%0x", 123).should == "7b" - sprintf("% x", 123).should == " 7b" - sprintf("%+x", 123).should == "+7b" - sprintf("%+0x", 123).should == "+7b" - sprintf("%+ x", 123).should == "+7b" - sprintf("% 0x", 123).should == " 7b" - - sprintf("%#x", 123).should == "0x7b" - sprintf("%#0x", 123).should == "0x7b" - sprintf("%# x", 123).should == " 0x7b" - sprintf("%#+x", 123).should == "+0x7b" - sprintf("%#+0x", 123).should == "+0x7b" - sprintf("%#+ x", 123).should == "+0x7b" - sprintf("%# 0x", 123).should == " 0x7b" - - sprintf("%8x", 123).should == " 7b" - sprintf("%08x", 123).should == "0000007b" - sprintf("% 8x", 123).should == " 7b" - sprintf("%+8x", 123).should == " +7b" - sprintf("%+08x", 123).should == "+000007b" - sprintf("%+ 8x", 123).should == " +7b" - sprintf("% 08x", 123).should == " 000007b" - - sprintf("%#8x", 123).should == " 0x7b" - sprintf("%#08x", 123).should == "0x00007b" - sprintf("%# 8x", 123).should == " 0x7b" - sprintf("%#+8x", 123).should == " +0x7b" - sprintf("%#+08x", 123).should == "+0x0007b" - sprintf("%#+ 8x", 123).should == " +0x7b" - sprintf("%# 08x", 123).should == " 0x0007b" - - sprintf("%8.10x", 123).should == "000000007b" - sprintf("%08.10x", 123).should == "000000007b" - sprintf("% 8.10x", 123).should == " 000000007b" - sprintf("%+8.10x", 123).should == "+000000007b" - sprintf("%+08.10x", 123).should == "+000000007b" - sprintf("%+ 8.10x", 123).should == "+000000007b" - sprintf("% 08.10x", 123).should == " 000000007b" - - sprintf("%10.8x", 123).should == " 0000007b" - sprintf("%010.8x", 123).should == " 0000007b" - sprintf("% 10.8x", 123).should == " 0000007b" - sprintf("%+10.8x", 123).should == " +0000007b" - sprintf("%+010.8x", 123).should == " +0000007b" - sprintf("%+ 10.8x", 123).should == " +0000007b" - sprintf("% 010.8x", 123).should == " 0000007b" - end - - describe "with format string that contains %{} sections" do - it "substitutes values for named references" do - sprintf("%{foo}f", {foo: 1}).should == "1f" - end - - it "raises KeyError when no matching key is in second argument" do - lambda { sprintf("%{foo}f", {}) }.should raise_error(KeyError) - end - end - - describe "with format string that contains %<> sections" do - it "formats values for named references" do - sprintf("%<foo>f", {foo: 1}).should == "1.000000" - end - - it "raises KeyError when no matching key is in second argument" do - lambda { sprintf("%<foo>f", {}) }.should raise_error(KeyError) - end - - it "raises ArgumentError if missing second named argument" do - lambda { sprintf("%<key><foo>d", {key: 1}) }.should raise_error(ArgumentError) - end - end - - describe "with negative values" do - describe "with format %x" do - it "precedes the number with '..'" do - sprintf("%0x", -123).should == "..f85" - sprintf("%#0x", -123).should == "0x..f85" - sprintf("%08x", -123).should == "..ffff85" - sprintf("%#08x", -123).should == "0x..ff85" - sprintf("%8.10x", -123).should == "..ffffff85" - sprintf("%08.10x", -123).should == "..ffffff85" - sprintf("%10.8x", -123).should == " ..ffff85" - sprintf("%010.8x", -123).should == " ..ffff85" - end - end - - describe "with format %b or %B" do - it "precedes the number with '..'" do - sprintf("%.7b", -5).should == "..11011" - sprintf("%.7B", -5).should == "..11011" - sprintf("%0b", -5).should == "..1011" - end - end - end - - it "passes some tests for negative %x" do - sprintf("%x", -123).should == "..f85" - sprintf("% x", -123).should == "-7b" - sprintf("%+x", -123).should == "-7b" - sprintf("%+0x", -123).should == "-7b" - sprintf("%+ x", -123).should == "-7b" - sprintf("% 0x", -123).should == "-7b" - - sprintf("%#x", -123).should == "0x..f85" - sprintf("%# x", -123).should == "-0x7b" - sprintf("%#+x", -123).should == "-0x7b" - sprintf("%#+0x", -123).should == "-0x7b" - sprintf("%#+ x", -123).should == "-0x7b" - sprintf("%# 0x", -123).should == "-0x7b" - - sprintf("%8x", -123).should == " ..f85" - sprintf("% 8x", -123).should == " -7b" - sprintf("%+8x", -123).should == " -7b" - sprintf("%+08x", -123).should == "-000007b" - sprintf("%+ 8x", -123).should == " -7b" - sprintf("% 08x", -123).should == "-000007b" - - sprintf("%#8x", -123).should == " 0x..f85" - sprintf("%# 8x", -123).should == " -0x7b" - sprintf("%#+8x", -123).should == " -0x7b" - sprintf("%#+08x", -123).should == "-0x0007b" - sprintf("%#+ 8x", -123).should == " -0x7b" - sprintf("%# 08x", -123).should == "-0x0007b" - - sprintf("% 8.10x", -123).should == "-000000007b" - sprintf("%+8.10x", -123).should == "-000000007b" - sprintf("%+08.10x", -123).should == "-000000007b" - sprintf("%+ 8.10x", -123).should == "-000000007b" - sprintf("% 08.10x", -123).should == "-000000007b" - - sprintf("% 10.8x", -123).should == " -0000007b" - sprintf("%+10.8x", -123).should == " -0000007b" - sprintf("%+010.8x", -123).should == " -0000007b" - sprintf("%+ 10.8x", -123).should == " -0000007b" - sprintf("% 010.8x", -123).should == " -0000007b" - end - - it "passes some tests for negative %u" do - sprintf("%u", -123).should == "-123" - sprintf("%0u", -123).should == "-123" - sprintf("%#u", -123).should == "-123" - sprintf("%#0u", -123).should == "-123" - sprintf("%8u", -123).should == " -123" - sprintf("%08u", -123).should == "-0000123" - sprintf("%#8u", -123).should == " -123" - sprintf("%#08u", -123).should == "-0000123" - - sprintf("%30u", -123).should == " -123" - sprintf("%030u", -123).should == "-00000000000000000000000000123" - - sprintf("%#30u", -123).should == " -123" - sprintf("%#030u", -123).should == "-00000000000000000000000000123" - - sprintf("%24.30u", -123).should == "-000000000000000000000000000123" - sprintf("%024.30u", -123).should == "-000000000000000000000000000123" - - sprintf("%#24.30u", -123).should == "-000000000000000000000000000123" - sprintf("%#024.30u", -123).should == "-000000000000000000000000000123" - - - sprintf("%30.24u", -123).should == " -000000000000000000000123" - sprintf("%030.24u", -123).should == " -000000000000000000000123" - - sprintf("%#30.24u", -123).should == " -000000000000000000000123" - sprintf("%#030.24u", -123).should == " -000000000000000000000123" - end - - it "passes some tests for positive %u" do - sprintf("%30u", 123).should == " 123" - sprintf("%030u", 123).should == "000000000000000000000000000123" - - sprintf("%#30u", 123).should == " 123" - sprintf("%#030u", 123).should == "000000000000000000000000000123" - - sprintf("%24.30u", 123).should == "000000000000000000000000000123" - sprintf("%024.30u", 123).should == "000000000000000000000000000123" - - sprintf("%#24.30u", 123).should == "000000000000000000000000000123" - sprintf("%#024.30u", 123).should == "000000000000000000000000000123" - - sprintf("%30.24u", 123).should == " 000000000000000000000123" - sprintf("%030.24u", 123).should == " 000000000000000000000123" - - sprintf("%#30.24u", 123).should == " 000000000000000000000123" - sprintf("%#030.24u", 123).should == " 000000000000000000000123" - end - - it "passes some tests for positive %d" do - sprintf("%30d", 123).should == " 123" - sprintf("%030d", 123).should == "000000000000000000000000000123" - - sprintf("%#30d", 123).should == " 123" - sprintf("%#030d", 123).should == "000000000000000000000000000123" - - sprintf("%24.30d", 123).should == "000000000000000000000000000123" - sprintf("%024.30d", 123).should == "000000000000000000000000000123" - - sprintf("%#24.30d", 123).should == "000000000000000000000000000123" - sprintf("%#024.30d", 123).should == "000000000000000000000000000123" - - sprintf("%30.24d", 123).should == " 000000000000000000000123" - sprintf("%030.24d", 123).should == " 000000000000000000000123" - - sprintf("%#30.24d", 123).should == " 000000000000000000000123" - sprintf("%#030.24d", 123).should == " 000000000000000000000123" - end - - it "passes some tests for positive %f" do - sprintf("%30f", 123.1).should == " 123.100000" - sprintf("%030f", 123.1).should == "00000000000000000000123.100000" - - sprintf("%#30f", 123.1).should == " 123.100000" - sprintf("%#030f", 123.1).should == "00000000000000000000123.100000" - - sprintf("%10.4f", 123.1).should == " 123.1000" - sprintf("%010.4f", 123.1).should == "00123.1000" - - sprintf("%10.0f", 123.1).should == " 123" - sprintf("%010.0f", 123.1).should == "0000000123" - end - - it "passes some tests for negative %f" do - sprintf("%30f", -123.5).should == " -123.500000" - sprintf("%030f", -123.5).should == "-0000000000000000000123.500000" - - sprintf("%#30f", -123.5).should == " -123.500000" - sprintf("%#030f", -123.5).should == "-0000000000000000000123.500000" - - sprintf("%10.4f", -123.5).should == " -123.5000" - sprintf("%010.4f", -123.5).should == "-0123.5000" - - sprintf("%10.0f", -123.5).should == " -124" - sprintf("%010.0f", -123.5).should == "-000000124" - end - - it "passes some tests for infinite and nan" do - sprintf("%f", Float::INFINITY).should == "Inf" - sprintf("%f", -Float::INFINITY).should == "-Inf" - sprintf("%f", Float::NAN).should == "NaN" - - sprintf("%10f", Float::INFINITY).should == " Inf" - sprintf("%10f", -Float::INFINITY).should == " -Inf" - sprintf("%10f", Float::NAN).should == " NaN" - end - - it "passes kstephens's tests" do - sprintf("%*1$.*2$3$d", 10, 5, 1).should == " 00001" - sprintf("%b", 0).should == "0" - sprintf("%B", 0).should == "0" - sprintf("%b", -5).should == "..1011" - sprintf("%B", -5).should == "..1011" - sprintf("%+b", -5).should == "-101" - sprintf("%+b", 10).should == "+1010" - sprintf("%+b", 0).should == "+0" - sprintf("%+o", -5).should == "-5" - sprintf("%+o", 10).should == "+12" - sprintf("%+o", 0).should == "+0" - sprintf("%+d", -5).should == "-5" - sprintf("%+d", 10).should == "+10" - sprintf("%+d", 0).should == "+0" - sprintf("%+x", -15).should == "-f" - sprintf("%+x", 100).should == "+64" - sprintf("%+x", 0).should == "+0" - sprintf("%+X", -15).should == "-F" - sprintf("%+X", 100).should == "+64" - sprintf("%+X", 0).should == "+0" - sprintf("=%02X", 1).should == "=01" - sprintf("%+03d", 0).should == "+00" - sprintf("%+03d", 5).should == "+05" - sprintf("%+03d", -5).should == "-05" - sprintf("%+03d", 12).should == "+12" - sprintf("%+03d", -12).should == "-12" - sprintf("%+03d", 123).should == "+123" - sprintf("%+03d", -123).should == "-123" - end - - with_feature :encoding do - it "returns a String in the same encoding as the format String if compatible" do - format = "%.2f %4s".force_encoding(Encoding::KOI8_U) - result = sprintf(format, 1.2, "dogs") - result.encoding.should equal(Encoding::KOI8_U) - end - - it "returns a String in the argument encoding if format encoding is more restrictive" do - format = "foo %s".force_encoding(Encoding::US_ASCII) - arg = "b\303\274r".force_encoding(Encoding::UTF_8) - - result = sprintf(format, arg) - result.encoding.should equal(Encoding::UTF_8) - end - end + it_behaves_like :kernel_sprintf_encoding, -> (format, *args) { + sprintf(format, *args) + } end describe "Kernel.sprintf" do - it "needs to be reviewed for spec completeness" + it_behaves_like :kernel_sprintf, -> (format, *args) { + Kernel.sprintf(format, *args) + } + + it_behaves_like :kernel_sprintf_encoding, -> (format, *args) { + Kernel.sprintf(format, *args) + } end |