There’s been an on-goingdiscussion in the ruby community recently about warnings. I’ve never used ruby’s -w option much, but I’m always in favor of more automated tools that can help improve my code. Furthermore, as a gem author, I strive to make my gems play nice with other ruby tools. Some rubyists will never run their code with -w, but for those who do, I don’t want my gems to generate warnings for them.
The primary gem I work on is VCR, and I decided that it was time to make it warning free.
Initial configuration
The rake task included with RSpec provides all the configuration options we need:
You’ll notice I set skip_bundler = true; when the specs were run with bundle exec, I discovered that the warnings were silenced. I’m not sure why, but there’s an open issue for it, so hopefully it’ll be fixed at some point. In the meantime, running the specs without bundle exec works fine since I setup bundler in my spec_helper file and only have one version of RSpec installed.
Too many warnings
After making this change, when I run my specs, I’m greeted with over 6500 lines of warnings like these:
warnings.txt
/Users/myron/code/vcr/lib/vcr.rb:146: warning: instance variable @turned_off not initialized
/Users/myron/code/vcr/spec/support/sinatra_app.rb:36: warning: instance variable @_boot_failed not initialized
/Users/myron/.rvm/gems/[email protected]/gems/excon-0.6.5/lib/excon/connection.rb:46: warning: instance variable @proxy not initialized
/Users/myron/.rvm/gems/[email protected]/gems/excon-0.6.5/lib/excon/connection.rb:46: warning: instance variable @proxy not initialized
/Users/myron/.rvm/gems/[email protected]/gems/faraday-0.7.4/lib/faraday/connection.rb:142: warning: instance variable @proxy not initialized
/Users/myron/.rvm/gems/[email protected]/gems/faraday-0.7.4/lib/faraday/connection.rb:142: warning: instance variable @proxy not initialized
/Users/myron/code/vcr/lib/vcr.rb:146: warning: instance variable @turned_off not initialized
/Users/myron/.rvm/gems/[email protected]/gems/typhoeus-0.2.4/lib/typhoeus/hydra.rb:124: warning: instance variable @cache_getter not initialized
…not exactly the most useful output. It’s a bit overwhelming, and beyond that, many of the warnings are coming from other libraries.
My solution
What I really wanted is a list of unique warnings coming from VCR. To that end, I came up with a way to filter and format the warnings:
capture_warnings.rb
require'tempfile'stderr_file=Tempfile.new("vcr.stderr")$stderr.reopen(stderr_file.path)current_dir=Dir.pwdat_exitdostderr_file.rewindlines=stderr_file.read.split("\n").uniqstderr_file.close!vcr_warnings,other_warnings=lines.partition{|line|line.include?(current_dir)}ifvcr_warnings.any?putsputs"-"*30+" VCR Warnings: "+"-"*30putsputsvcr_warnings.join("\n")putsputs"-"*75putsendifother_warnings.any?File.open('tmp/warnings.txt','w'){|f|f.write(other_warnings.join("\n"))}putsputs"Non-VCR warnings written to tmp/warnings.txt"putsend# fail the build...exit(1)ifvcr_warnings.any?end
Rakefile
RSpec::Core::RakeTask.new(:spec) do |t|
t.ruby_opts = "-w -r./capture_warnings"
t.skip_bundler = true
end
In a nutshell, here’s how it works:
I redirect stderr into a temporary file, since ruby prints warnings to stderr.
I pass ruby a -r ./capture_warnings.rb option so that this file gets loaded first and ensures all warnings get captured.
In the at_exit hook, I reformat the output, printing only unique warnings that originate from files under the current directory (which is a good clue that VCR is responsible for them).
Finally, I exit with a non-zero status if there are any warnings from VCR. This will help me keep VCR warning-free in the future by failing the build and forcing me to remove the warnings.
This made the output much more useful:
warnings_output.txt
------------------------------ VCR Warnings: ------------------------------/Users/myron/code/vcr/spec/vcr/cassette/reader_spec.rb:24: warning: useless use of == in void context
/Users/myron/code/vcr/spec/support/shared_example_groups/ignore_localhost_deprecation.rb:15: warning: `*' interpreted as argument prefix
/Users/myron/code/vcr/spec/support/shared_example_groups/normalizers.rb:1: warning: loading in progress, circular require considered harmful - /Users/myron/code/vcr/spec/spec_helper.rb
from /Users/myron/code/vcr/spec/vcr/cassette/reader_spec.rb:1:in `<top (required)>'
from /Users/myron/code/vcr/spec/vcr/cassette/reader_spec.rb:1:in `require'
from /Users/myron/code/vcr/spec/spec_helper.rb:14:in `<top (required)>'
from /Users/myron/code/vcr/spec/spec_helper.rb:14:in `each'
from /Users/myron/code/vcr/spec/spec_helper.rb:14:in `block in <top (required)>'
from /Users/myron/code/vcr/spec/spec_helper.rb:14:in `require'
from /Users/myron/code/vcr/spec/support/shared_example_groups/normalizers.rb:1:in `<top (required)>'
from /Users/myron/code/vcr/spec/support/shared_example_groups/normalizers.rb:1:in `require'
/Users/myron/code/vcr/lib/vcr/config.rb:47: warning: `*' interpreted as argument prefix
/Users/myron/code/vcr/lib/vcr.rb:146: warning: instance variable @turned_off not initialized
/Users/myron/code/vcr/lib/vcr/http_stubbing_adapters/fakeweb.rb:70: warning: instance variable @http_connections_allowed not initialized
---------------------------------------------------------------------------Non-VCR warnings written to tmp/warnings.txt
My solution may not be your solution
This is working really well for me, and I just released VCR 1.11.2 yesterday with all warnings fixed. That said, my solution definitely makes a couple assumptions that may not be true of every project:
It uses a very simple heuristic (line.include?(current_dir)) to figure out if VCR is responsible for the warning. Does ruby always include the current working directory in the output of every warning that originates from code in your current working directory? That seems to be the case so far but I have no idea if this is true. 1
It assumes that all stderr output is from warnings. This is true for my project but may not be true for yours.
I’m curious if others have come up with better or alternate solutions to the problem of isolating the warnings output to just the ones your code is responsible for. Please let me know in the comments!