Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add specs for Kernel#sprintf #537

Merged
merged 16 commits into from
Nov 17, 2017
Merged

Add specs for Kernel#sprintf #537

merged 16 commits into from
Nov 17, 2017

Conversation

andrykonchin
Copy link
Member

@andrykonchin andrykonchin commented Oct 28, 2017

Related issue #68

Add specs for Kernel#sprintf and all related methods:

  • Kernel #printf/.printf/.sprintf
  • File, StringIO #printf
  • String#%

Not sure whether we should test ARGF.printf and IO#printf

@andrykonchin
Copy link
Member Author

andrykonchin commented Oct 31, 2017

@eregon There are several issues marked with # strange behavior comments. Not sure they are bugs or just documentation isn't full

Looks like TravisCI integration is broken and build failed

@andrykonchin andrykonchin changed the title [WIP] Add specs for Kernel#sprintf Add specs for Kernel#sprintf Oct 31, 2017
@andrykonchin
Copy link
Member Author

Build fails on AppVeyor on both 2.3 and 2.4

File#printf other formats c supports Unicode characters FAILED
Expected "\xD4\x86"
 to equal "\u0506"
C:/projects/spec-x948i/core/kernel/shared/sprintf.rb:413:in `block (4 levels) in <top (required)>'
C:/projects/spec-x948i/core/file/printf_spec.rb:4:in `<top (required)>'
Finished in 262.352770 seconds

Specs run successfully on OS X so it's maybe an issue with Windows.

@andrykonchin
Copy link
Member Author

On TravisCI some some not relevant tests failed

@andrykonchin andrykonchin self-assigned this Nov 1, 2017
require File.expand_path('../../../spec_helper', __FILE__)
require File.expand_path('../../kernel/shared/sprintf', __FILE__)

describe "File#printf" do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's IO#printf:

[1] pry(main)> File.new("README.md").method :printf
=> #<Method: File(IO)#printf>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon Got it

I checked owner for StringIO and have found it is IO::writable in Ruby 2.2 and IO::generic_writable in Ruby 2.3/2.4. It looks like an implementation detail and I wonder if I should test printf for both IO::writable and IO::generic_writable

StringIO.new.method(:printf).owner
=> IO::writable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting.
Those modules are definitely internal, even though they show up in ancestors:

> StringIO.ancestors
=> [StringIO,
 IO::generic_writable,
 IO::generic_readable,
 Enumerable,
 Data,
 Object,
 PP::ObjectMixin,
 Kernel,
 BasicObject]

So I think it's best to share specs as possible and just have the specs in StringIO#printf, since modules above are internal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make this a spec for IO#printf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon do you propose to test only IO#printf or share the specs for ancestors too (File/StringIO)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon ping

@eregon
Copy link
Member

eregon commented Nov 2, 2017

That's a big and awesome PR!
It might take me a while to review these 1000 lines of spec, I'll try to review progressively.

Re Travis, it looks like a Travis issue to me.
Did you see it in other builds or is it spurious?

@andrykonchin
Copy link
Member Author

andrykonchin commented Nov 3, 2017

@eregon

Did you see it in other builds or is it spurious?

I reproduce it constantly when restart builds. Looks like builds in other new PRs fail too

What do you think about failed build on AppVeyor and issue with file and encoding on Windows?

@mjago
Copy link
Contributor

mjago commented Nov 3, 2017

The same travis fail is happening on Ruby trunk too.

@mjago
Copy link
Contributor

mjago commented Nov 3, 2017

@eregon Looks like the CI failures are due to localhost resolving to ::1 (IPV6) which travis doesn't like [(such as here)

Ruby CI builds are failing for the same reason. Unfortunately sockets are well outside my comfort zone so I will not be able to resolve) 😊

@eregon
Copy link
Member

eregon commented Nov 4, 2017

I fixed Travis in 7d76b72, if you rebase there should not be bind(2) failures anymore.

@andrykonchin
Copy link
Member Author

Looks like some strange warnings of rubocop failed the build on TravisCI.

@eregon
Copy link
Member

eregon commented Nov 6, 2017

Re RuboCop, we should disable Lint/FormatParameterMismatch for the file core/kernel/shared/sprintf.rb in .rubocop.yml. There are already examples in that file how to do that.
Here the warnings are not interesting since we want to test edge cases with sprintf & friends.

Copy link
Member

@eregon eregon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great start!
That's smart, passing a lambda to it_behaves_like, well done.
Could you take a look at my comments and address them?

end
string = File.read(@filename)

string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string variable is not needed here.


printf(format, *args)

$stdout = stdout
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should be in ensure so that if printf raises we still get back a normal $stdout.

end

it "returns a String in the same encoding as the format String if compatible" do
string = "%s".force_encoding(Encoding::KOI8_U)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I didn't know this encoding before :)


result = format(string, argument)
result.encoding.should equal(Encoding::UTF_8)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if format is KOI8_U with non-7-bit characters and the argument UTF-8 with non-7-bit characters?
Could you add a spec for that?
Maybe also one with incompatible encodings (e.g. UTF-16BE and a UTF-8).

def obj.to_i; 0 end
def obj.to_int; 1 end

format("%b", obj).should == "1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a note, you could encode this constraint in the first spec with
obj.should_not_receive(:to_i).
But this if fine too.

format("%020A", 196).should == "0X000000000001.88P+7"
end

it "uses radix-1 when displays negative argument as a two's complemen" do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

complement

describe "d" do
it "converts argument as a decimal number" do
format("%d", 112).should == "112"
format("%d", -112).should == "-112"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to test %d with a Bignum (> 2**64)


format("%1$*2$c", 97, 10).should == " a"
format("%1$*2$p", [], 10).should == " []"
format("%1$*2$s", "abc", 10).should == " abc"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimate corner-case, can we have format("%1$*2$s", "abc", -10)?

# strange behavior
it "does not affect G format" do
format("%.10g", 12.1234).should == "12.1234" # expected "12.1234000000"
format("%.10g", 123456789).should == "123456789" # expected "1.2345700000e+08"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a bug, could you report it? (I think a single issue asking multiple strange behaviors for sprintf is OK)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First case: is ok. g does not print trailing zeroes, but there's the # flag if you want that.
Second case: also ok. Precision is 10, the integer has 9 digits so fits without resorting to scientific notation. Use two more digits, or two less precision, to get scientific notation.

Doc says:

Convert a floating point number using exponential form
if the exponent is [...] or greater than or
equal to the precision, [...]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification.

end

# strange behavior
# expected to call to_str and only then to_s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's OK, see my comment above about to_s/to_str.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@eregon
Copy link
Member

eregon commented Nov 6, 2017

@marcandre If you have a bit of time, could you take a look at the cases @andrykonchin annotated as strange behavior and share your thoughts?

@marcandre
Copy link
Member

Wow, ambitious PR!
I commented on the specific strange behaviour cases, HTH.

@marcandre
Copy link
Member

@andrykonchin great job on doubting the results you get, btw. It's important to be on the lookout for bugs when spec'ing

# irb(main):020:0* sprintf "Ä %s".encode('windows-1252'), "Ђ".encode('utf-8')
# Encoding::CompatibilityError: incompatible character encodings: Windows-1252 and UTF-8
# expected to return string in UTF-8 encoding
it "raises Encoding::CompatibilityError if cannot convert argument and format to any of encodings" do
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon There is another example of strange behavior

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmh, so those encoding are compatible according to Encoding.compatible?:

> Encoding.compatible? "windows-1252", "UTF-8"
=> #<Encoding:UTF-8>

But "Ђ" cannot be encoded in Windows-1252:

[13] pry(main)> "Ђ".encode('windows-1252')
Encoding::UndefinedConversionError: U+0402 to WINDOWS-1252 in conversion from UTF-8 to WINDOWS-1252
from (pry):12:in `encode'

Copy link
Member Author

@andrykonchin andrykonchin Nov 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon right,

Feature is
it returns a String in the argument's encoding if format encoding is more restrictive.

In the example in comments Ruby can convert format string (in 'windows-1252') to utf-8 but doesn't do this.

Copy link
Member

@eregon eregon Nov 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's basically a consequence that

> "Ä %s".encode('windows-1252') << "Ђ".encode('utf-8')
Encoding::CompatibilityError: incompatible character encodings: Windows-1252 and UTF-8

I think Ruby never tries to re-encode a String, unless you use encode explicitly.
The resulting encoding seems therefore to be the resulting encoding of << each %s argument .

But you are right - it's responsibility of string concatenation algorithm.

Copy link
Member Author

@andrykonchin andrykonchin Nov 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon maybe, but it looks like a bug to me, because here this feature works

UPD
Hm, according to this spec we should really get exception, but why we don't get it in "returns a String in the argument's encoding if format encoding is more restrictive" spec? UTF-8 string contains not ASCII characters

But you are right - it's responsibility of string concatenation algorithm

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But here the "foo %s" is ASCII-only, so it works fine.
There is special treatment for Strings with only 7-bit characters for many String operations (in that case the Encoding usually doesn't matter, it's like if it was US-ASCII).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon right

@andrykonchin
Copy link
Member Author

@eregon done, except sharing

@andrykonchin
Copy link
Member Author

@eregon ping
I fixed all the notes. Have I missed anything?

@eregon
Copy link
Member

eregon commented Nov 15, 2017

We just to make the Windows CI (AppVeyor) pass :)

1)
File#printf other formats c supports Unicode characters FAILED
Expected "\xD4\x86"
 to equal "\u0506"
C:/projects/spec-x948i/core/kernel/shared/sprintf.rb:309:in `block (4 levels) in <top (required)>'
C:/projects/spec-x948i/core/file/printf_spec.rb:4:in `<top (required)>'
Finished in 339.136043 seconds

Probably the file should be opened with an encoding argument, since UTF-8 is not the default on Windows.

@andrykonchin
Copy link
Member Author

@eregon done

Copy link
Member

@eregon eregon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrykonchin Thank you for this awesome PR!

@eregon
Copy link
Member

eregon commented Nov 17, 2017

Let's merge this!
@andrykonchin Sorry, I was fairly busy recently so I took a while to reply.
Should be much better from now on :)

@eregon eregon merged commit f2f23ad into ruby:master Nov 17, 2017
@andrykonchin andrykonchin deleted the add-specs-for-printf branch November 17, 2017 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants