From a10730304895d7e0706c09b57794cf857da5e7f5 Mon Sep 17 00:00:00 2001 From: till Date: Sat, 11 Feb 2023 17:50:00 +0100 Subject: [PATCH] Chore: lint fixes and general qa - ran rubocop on the code (with autofix) - refactored test setup - refactored code (e.g. request libraries) - added test coverage for the library code --- Gemfile | 2 + Makefile | 6 ++ Rakefile | 27 +++---- apt-spy2.gemspec | 5 +- bin/apt-spy2 | 5 +- lib/apt/spy2.rb | 136 ++++++++++++++------------------- lib/apt/spy2/country.rb | 17 ++--- lib/apt/spy2/downloader.rb | 31 ++++---- lib/apt/spy2/launchpad.rb | 11 ++- lib/apt/spy2/request.rb | 30 ++++++++ lib/apt/spy2/ubuntu_mirrors.rb | 12 ++- lib/apt/spy2/url.rb | 17 +++-- lib/apt/spy2/version.rb | 4 +- lib/apt/spy2/writer.rb | 37 +++++---- test_helper.rb | 30 ++++++++ tests/country_test.rb | 12 +-- tests/downloader_test.rb | 17 +++-- tests/launchpad_test.rb | 10 ++- tests/ubuntu_mirrors_test.rb | 15 ++++ tests/url_test.rb | 23 ++++++ tests/writer_test.rb | 27 +++++++ 21 files changed, 294 insertions(+), 180 deletions(-) create mode 100644 lib/apt/spy2/request.rb create mode 100644 test_helper.rb create mode 100644 tests/ubuntu_mirrors_test.rb create mode 100644 tests/url_test.rb create mode 100644 tests/writer_test.rb diff --git a/Gemfile b/Gemfile index fa75df1..7f4f5e9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' gemspec diff --git a/Makefile b/Makefile index 9bff7d3..6148f9a 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,12 @@ install: release: bundle exec rake release +lint: + bundle exec rubocop . + +test: install + bundle exec rake test + docker-build: docker build -t $(IMAGE) . diff --git a/Rakefile b/Rakefile index 15fc9e6..b51877a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,24 +1,17 @@ -require "bundler/gem_tasks" -require "minitest/autorun" -require "simplecov" -require "simplecov-lcov" +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rake/testtask' Encoding.default_external = Encoding::UTF_8 Encoding.default_internal = Encoding::UTF_8 -task :test do +Rake::TestTask.new do |t| ENV['COVERAGE'] = 'true' + t.pattern = 'tests/*_test.rb' - SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter - SimpleCov::Formatter::LcovFormatter.config do |c| - c.output_directory = './coverage' - c.report_with_single_file = true - c.single_report_path = './coverage/lcov.info' - end - SimpleCov.start - - $LOAD_PATH.unshift('lib', 'tests') - Dir.glob('./tests/*_test.rb') do |f| - require f - end + # $LOAD_PATH.unshift('lib', 'tests') + # Dir.glob('./tests/*_test.rb').sort.each do |f| + # require f + # end end diff --git a/apt-spy2.gemspec b/apt-spy2.gemspec index 76f8fdc..b65e976 100644 --- a/apt-spy2.gemspec +++ b/apt-spy2.gemspec @@ -1,3 +1,6 @@ +# frozen_string_literal: true + +# require 'English' lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'apt/spy2/version' @@ -14,7 +17,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.7', '< 3.3' - spec.files = `git ls-files`.split($/) + spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] diff --git a/bin/apt-spy2 b/bin/apt-spy2 index 716db01..83172b3 100755 --- a/bin/apt-spy2 +++ b/bin/apt-spy2 @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require 'apt/spy2' require 'colored' @@ -8,7 +9,7 @@ Encoding.default_internal = Encoding::UTF_8 begin AptSpy2.start -rescue => the_error - puts the_error.to_s.white_on_red +rescue StandardError => e + puts e.to_s.white_on_red exit 1 end diff --git a/lib/apt/spy2.rb b/lib/apt/spy2.rb index c32d3da..af0382b 100755 --- a/lib/apt/spy2.rb +++ b/lib/apt/spy2.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'thor' require 'open-uri' require 'colored' @@ -7,39 +9,39 @@ require 'apt/spy2/downloader' require 'apt/spy2/ubuntu_mirrors' require 'apt/spy2/launchpad' +require 'apt/spy2/request' require 'apt/spy2/url' -require 'net/http' -require 'net/https' -require 'uri' +# apt-spy2 command interface class AptSpy2 < Thor - package_name "apt-spy2" + package_name 'apt-spy2' - desc "fix", "Set the closest/fastest mirror" - option :country, :default => "mirrors" - option :commit, :type => :boolean - option :launchpad, :type => :boolean, :banner => "Use launchpad's mirror list" - option :strict, :type => :boolean + desc 'fix', 'Set the closest/fastest mirror' + option :country, default: 'mirrors' + option :commit, type: :boolean + option :launchpad, type: :boolean, banner: "Use launchpad's mirror list" + option :strict, type: :boolean def fix - working = filter(retrieve(options[:country], use_launchpad?(options)), options[:strict], false) - print "The closest mirror is: " - puts "#{working[0]}".bold.magenta - if !options[:commit] - puts "Run with --commit to adjust /etc/apt/sources.list".yellow - else - puts "Updating /etc/apt/sources.list".yellow - update(working[0]) + mirrors = retrieve(options[:country], use_launchpad?(options)) + working = filter(mirrors, options[:strict], false) + print 'The closest mirror is: ' + puts (working[0]).to_s.bold.magenta + unless options[:commit] + puts 'Run with --commit to adjust /etc/apt/sources.list'.yellow + return end + + puts 'Updating /etc/apt/sources.list'.yellow + update(working[0]) end - desc "check", "Evaluate mirrors" - option :country, :default => "mirrors" - option :output, :type => :boolean, :default => true - option :format, :default => "shell" - option :launchpad, :type => :boolean, :banner => "Use launchpad's mirror list" - option :strict, :type => :boolean + desc 'check', 'Evaluate mirrors' + option :country, default: 'mirrors' + option :output, type: :boolean, default: true + option :format, default: 'shell' + option :launchpad, type: :boolean, banner: "Use launchpad's mirror list" + option :strict, type: :boolean def check - @writer = Apt::Spy2::Writer.new(options[:format]) mirrors = retrieve(options[:country], use_launchpad?(options)) @@ -48,50 +50,47 @@ def check puts @writer.to_json if @writer.json? end - desc "list", "List the currently available mirrors" - option :country, :default => "mirrors" - option :format, :default => "shell" - option :launchpad, :type => :boolean, :banner => "Use launchpad's mirror list" + desc 'list', 'List the currently available mirrors' + option :country, default: 'mirrors' + option :format, default: 'shell' + option :launchpad, type: :boolean, banner: "Use launchpad's mirror list" def list mirrors = retrieve(options[:country], use_launchpad?(options)) @writer = Apt::Spy2::Writer.new(options[:format]) - @writer.set_complete(mirrors) + @writer.complete(mirrors) puts @writer.to_json if @writer.json? - puts mirrors if !@writer.json? + puts mirrors unless @writer.json? end - desc "version", "Show which version of apt-spy2 is installed" + desc 'version', 'Show which version of apt-spy2 is installed' def version puts Apt::Spy2::VERSION exit end private - def retrieve(country = "mirrors", launchpad = false) + def retrieve(country = 'mirrors', launchpad = false) downloader = Apt::Spy2::Downloader.new - if launchpad === true - csv_path = File.expand_path(File.dirname(__FILE__) + "/../../var/country-names.txt") + if launchpad + csv_path = File.expand_path("#{File.dirname(__FILE__)}/../../var/country-names.txt") country = Apt::Spy2::Country.new(csv_path) name = country.to_country_name(options[:country]) launchpad = Apt::Spy2::Launchpad.new(downloader.do_download('https://launchpad.net/ubuntu/+archivemirrors')) - return launchpad.get_mirrors(name) + return launchpad.mirrors(name) end country.upcase! if country.length == 2 ubuntu_mirrors = Apt::Spy2::UbuntuMirrors.new(downloader.do_download("http://mirrors.ubuntu.com/#{country}.txt")) - mirrors = ubuntu_mirrors.get_mirrors(country) - return mirrors - + ubuntu_mirrors.mirrors(country) end - private def filter(mirrors, strict = false, output = true) # f me :) @@ -100,56 +99,40 @@ def filter(mirrors, strict = false, output = true) url = Apt::Spy2::Url.new(strict) mirrors.each do |mirror| - data = {"mirror" => mirror } + data = { 'mirror' => mirror } - check = url.get_mirror(mirror) + check = url.adjust!(mirror) status = broken?(check) - data["status"] = status + data['status'] = status - if status == "up" - working_mirrors << mirror - end + working_mirrors << mirror if status == 'up' @writer.echo(data) if output end - return working_mirrors + working_mirrors end - private def broken?(url) - uri = URI(url) - - http = Net::HTTP.new(uri.host, uri.port) - if uri.scheme == "https" - http.use_ssl = true - end - begin - response = http.request(Net::HTTP::Head.new(uri.request_uri)) - if response.code == "200" - return "up" - end + req = Apt::Spy2::Request.new(url) + response = req.head + return 'up' if response.code == '200' - if response.code == "404" - return "broken" - end - - return "down" - rescue Exception => e + return 'broken' if response.code == '404' + rescue StandardError # connection errors, ssl errors, etc. - return "down" end + + 'down' end - private def update(mirror) - t = Time.now - r = `lsb_release -c`.split(" ")[1] - sources = "## Updated on #{t.to_s} by apt-spy2\n" + r = `lsb_release -c`.split(' ')[1] + sources = "## Updated on #{t} by apt-spy2\n" sources << "deb #{mirror} #{r} main restricted universe multiverse\n" sources << "deb #{mirror} #{r}-updates main restricted universe multiverse\n" sources << "deb #{mirror} #{r}-backports main restricted universe multiverse\n" @@ -162,26 +145,23 @@ def update(mirror) File.open(apt_sources, 'w') do |f| f.write(sources) end - rescue + rescue StandardError msg = "Failed updating #{apt_sources}!" - msg << "You probably need sudo!" + msg << 'You probably need sudo!' raise msg end puts "Updated '#{apt_sources}' with #{mirror}".green - puts "Run `apt-get update` to update".black_on_yellow + puts 'Run `apt-get update` to update'.black_on_yellow end - private def use_launchpad?(options) - if !options[:launchpad] - return false - end + return false unless options[:launchpad] if options[:country] && options[:country] == 'mirrors' - raise "Please supply a `--country=foo`. Launchpad cannot guess!" + raise 'Please supply a `--country=foo`. Launchpad cannot guess!' end - return true + true end end diff --git a/lib/apt/spy2/country.rb b/lib/apt/spy2/country.rb index 27c52e5..f693f80 100644 --- a/lib/apt/spy2/country.rb +++ b/lib/apt/spy2/country.rb @@ -1,33 +1,32 @@ +# frozen_string_literal: true + module Apt module Spy2 + # lookup a country based on code class Country - def initialize(country_database) @database = country_database end def to_country_name(code) code = code.upcase - return capitalize(code) unless code.length == 2 + return capitalize!(code) unless code.length == 2 File.open(@database).each do |line| country, tld = line.split(';', 2) tld.gsub!(/\n/, '') - if code == tld - return capitalize(country) - end - + return capitalize!(country) if code == tld end raise "Could not look up: #{code}" end private - def capitalize(str) - return str.split(" ").map(&:capitalize).join(" ") - end + def capitalize!(str) + str.split(' ').map(&:capitalize).join(' ') + end end end end diff --git a/lib/apt/spy2/downloader.rb b/lib/apt/spy2/downloader.rb index c68960c..d1d1d32 100644 --- a/lib/apt/spy2/downloader.rb +++ b/lib/apt/spy2/downloader.rb @@ -1,30 +1,27 @@ -require 'open-uri' +# frozen_string_literal: true + +require 'apt/spy2/request' module Apt module Spy2 + # download url (e.g. mirror list or launchpad page) class Downloader - - def initialize(url = nil) - @url = url if !url.nil? - end - def do_download(url = nil) - @url = url if !url.nil? + raise 'Please supply a url.' if url.nil? - raise "Please supply a url." if url.nil? + req = Apt::Spy2::Request.new(url) begin - return URI.open(@url, :read_timeout => 10).read - rescue OpenURI::HTTPError => the_error - case the_error.io.status[0] - when "404" - raise "The URL #{@url} does not exist." - else - raise "Status: #{the_error.io.status[0]}" - end + response = req.get + return response.body if response.code == '200' + + raise "The URL #{@url} does not exist." if response.code == '404' + + raise "Status code: #{response.code}" + rescue StandardError => e + raise e end end - end end end diff --git a/lib/apt/spy2/launchpad.rb b/lib/apt/spy2/launchpad.rb index 283eb30..bd59657 100644 --- a/lib/apt/spy2/launchpad.rb +++ b/lib/apt/spy2/launchpad.rb @@ -1,15 +1,16 @@ +# frozen_string_literal: true + require 'nokogiri' module Apt module Spy2 + # parse launchpad output class Launchpad - def initialize(download) @launchpad = download end - def get_mirrors(country) - + def mirrors(country) mirrors = [] document = Nokogiri::HTML(@launchpad) @@ -27,10 +28,8 @@ def get_mirrors(country) end end - return mirrors - + mirrors end - end end end diff --git a/lib/apt/spy2/request.rb b/lib/apt/spy2/request.rb new file mode 100644 index 0000000..fc61db9 --- /dev/null +++ b/lib/apt/spy2/request.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'net/http' +require 'net/https' +require 'uri' + +module Apt + module Spy2 + # make requests + class Request + def initialize(url) + uri = URI(url) + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if uri.scheme == 'https' + + @http = http + @request_uri = uri.request_uri + end + + def get + @http.request(Net::HTTP::Get.new(@request_uri)) + end + + def head + @http.request(Net::HTTP::Head.new(@request_uri)) + end + end + end +end diff --git a/lib/apt/spy2/ubuntu_mirrors.rb b/lib/apt/spy2/ubuntu_mirrors.rb index bcaba2f..017ca18 100644 --- a/lib/apt/spy2/ubuntu_mirrors.rb +++ b/lib/apt/spy2/ubuntu_mirrors.rb @@ -1,19 +1,17 @@ +# frozen_string_literal: true + module Apt module Spy2 + # wraps around the \n delimited list of mirrors class UbuntuMirrors - def initialize(download) @ubuntu_mirrors = download end - def get_mirrors(country) - + def mirrors(_country) mirrors = @ubuntu_mirrors - mirrors = mirrors.split(/\n/) - return mirrors - + mirrors.split(/\n/) end - end end end diff --git a/lib/apt/spy2/url.rb b/lib/apt/spy2/url.rb index d9a8a45..3ea3a0d 100644 --- a/lib/apt/spy2/url.rb +++ b/lib/apt/spy2/url.rb @@ -1,24 +1,27 @@ +# frozen_string_literal: true + module Apt module Spy2 + # build mirror url class Url def initialize(strict) @strict = strict end - def get_mirror(mirror) + def adjust!(mirror) return mirror unless @strict - return "#{mirror}dists/#{get_release()}/Contents-#{get_arch()}.gz" + "#{mirror}dists/#{release}/Contents-#{arch}.gz" end private - def get_arch() - return `dpkg --print-architecture`.strip + + def arch + `dpkg --print-architecture`.strip end - private - def get_release() - return `lsb_release -c`.split(" ")[1].strip + def release + `lsb_release -c`.split(' ')[1].strip end end end diff --git a/lib/apt/spy2/version.rb b/lib/apt/spy2/version.rb index 0e8dc58..ae94d18 100644 --- a/lib/apt/spy2/version.rb +++ b/lib/apt/spy2/version.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module Apt module Spy2 - VERSION = "0.8.0" + VERSION = '0.8.0' end end diff --git a/lib/apt/spy2/writer.rb b/lib/apt/spy2/writer.rb index f438f48..18dd696 100644 --- a/lib/apt/spy2/writer.rb +++ b/lib/apt/spy2/writer.rb @@ -1,20 +1,20 @@ +# frozen_string_literal: true + require 'colored' require 'json' module Apt module Spy2 + # abstracted puts or json class Writer def initialize(format) - - if !["json", "shell"].include?(format) - raise "Unknown format: #{format}" - end + raise "Unknown format: #{format}" unless %w[json shell].include?(format) @format = format @complete = [] end - def set_complete(complete) + def complete(complete) @complete = complete end @@ -24,32 +24,29 @@ def echo(data) return end - print "Mirror: #{data["mirror"]} - " + print "Mirror: #{data['mirror']} - " - case data["status"] - when "up" - puts data["status"].upcase.green - when "down" - puts data["status"].upcase.red - when "broken" - puts data["status"].upcase.yellow + case data['status'] + when 'up' + puts data['status'].upcase.green + when 'down' + puts data['status'].upcase.red + when 'broken' + puts data['status'].upcase.yellow else - puts "Unknown status: #{data["status"]}".white_on_red + puts "Unknown status: #{data['status']}".white_on_red end end def json? - if @format == 'json' - return true - end + return true if @format == 'json' - return false + false end - def to_json + def to_json(*_args) JSON.generate(@complete) end - end end end diff --git a/test_helper.rb b/test_helper.rb new file mode 100644 index 0000000..3a0ef19 --- /dev/null +++ b/test_helper.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'simplecov' +require 'simplecov-lcov' + +if ENV['CI'] + SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter + SimpleCov::Formatter::LcovFormatter.config do |c| + c.output_directory = './coverage' + c.report_with_single_file = true + c.single_report_path = './coverage/lcov.info' + end +else + SimpleCov.formatter SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::SimpleFormatter, + SimpleCov::Formatter::HTMLFormatter + ]) +end + +SimpleCov.profiles.define 'apt-spy2' do + add_filter '/tests/' + add_filter '/pkg/' + add_filter '/vendor/' + add_filter '/var' + + add_group 'lib', 'lib/apt' + add_group 'bin', 'bin' +end +SimpleCov.start 'apt-spy2' +require 'minitest/autorun' diff --git a/tests/country_test.rb b/tests/country_test.rb index e742920..72eef31 100644 --- a/tests/country_test.rb +++ b/tests/country_test.rb @@ -1,12 +1,15 @@ -require 'apt/spy2/country' +# frozen_string_literal: true +require_relative '../test_helper' +require_relative '../lib/apt/spy2/country' + +# test the name resolution class CountryTest < Minitest::Test def setup - @country_list = File.expand_path(File.dirname(__FILE__) + "/../var/country-names.txt") + @country_list = File.expand_path("#{File.dirname(__FILE__)}/../var/country-names.txt") end - def test_tld_to_name() - + def test_tld_to_name # fixtures for people who don't want to read about rails data = { 'de' => 'Germany', @@ -22,6 +25,5 @@ def test_tld_to_name() data.each_pair do |code, expected| assert_equal(expected, c.to_country_name(code)) end - end end diff --git a/tests/downloader_test.rb b/tests/downloader_test.rb index 2ff3a4a..5355429 100644 --- a/tests/downloader_test.rb +++ b/tests/downloader_test.rb @@ -1,15 +1,18 @@ -require 'apt/spy2/downloader' +# frozen_string_literal: true +require_relative '../test_helper' +require_relative '../lib/apt/spy2/downloader' + +# this is an integration test (needs interwebs) class DownloaderTest < Minitest::Test - # this is an integration test (needs interwebs) - def test_do() + def test_do downloader = Apt::Spy2::Downloader.new - data = downloader.do_download("http://mirrors.ubuntu.com/DE.txt") - assert(!data.empty?, "There should have been a response, unless the mirrors are down.") + data = downloader.do_download('http://mirrors.ubuntu.com/DE.txt') + assert(!data.empty?, 'There should have been a response, unless the mirrors are down.') end - def test_do_wrong_countrys() + def test_do_wrong_countrys downloader = Apt::Spy2::Downloader.new - assert_raises(RuntimeError) { downloader.do_download("http://mirrors.ubuntu.com/de.txt") } + assert_raises(RuntimeError) { downloader.do_download('http://mirrors.ubuntu.com/de.txt') } end end diff --git a/tests/launchpad_test.rb b/tests/launchpad_test.rb index 4b5cdb3..d76c18a 100644 --- a/tests/launchpad_test.rb +++ b/tests/launchpad_test.rb @@ -1,13 +1,17 @@ -require 'apt/spy2/launchpad' +# frozen_string_literal: true +require_relative '../test_helper' +require_relative '../lib/apt/spy2/launchpad' + +# test to confirm launchpad format (HTML) is parsed class LaunchpadTest < Minitest::Test def setup - @download_fixture = File.read(File.expand_path(File.dirname(__FILE__) + "/fixtures/launchpad.html")) + @download_fixture = File.read(File.expand_path("#{File.dirname(__FILE__)}/fixtures/launchpad.html")) end def test_german_mirrors lp = Apt::Spy2::Launchpad.new(@download_fixture) - mirrors = lp.get_mirrors('Germany') + mirrors = lp.mirrors('Germany') assert_equal(false, mirrors.empty?) end end diff --git a/tests/ubuntu_mirrors_test.rb b/tests/ubuntu_mirrors_test.rb new file mode 100644 index 0000000..ce6eed3 --- /dev/null +++ b/tests/ubuntu_mirrors_test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative '../test_helper' +require_relative '../lib/apt/spy2/ubuntu_mirrors' + +# test to confirm launchpad format (HTML) is parsed +class UbuntuMirrorsTest < Minitest::Test + def test_mirrors + expected = ['http://example.org', 'http://example.de'] + fixture = expected.join("\n") + + m = Apt::Spy2::UbuntuMirrors.new(fixture) + assert_equal(expected, m.mirrors('country')) + end +end diff --git a/tests/url_test.rb b/tests/url_test.rb new file mode 100644 index 0000000..0530fbc --- /dev/null +++ b/tests/url_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative '../test_helper' +require_relative '../lib/apt/spy2/url' + +# test to check request lib +class UrlTest < Minitest::Test + def test_non_strict + expected = 'http://example.org' + + url = Apt::Spy2::Url.new(false) + assert_equal(expected, url.adjust!(expected)) + end + + # def strict + # skip "Test only runs on ubuntu" if OS.unix? and not OS.mac? + + # expected = 'http://example.org' + + # url = Apt::Spy2::Url.new(true) + # assert_not_equal(expected, url.mirror) + # end +end diff --git a/tests/writer_test.rb b/tests/writer_test.rb new file mode 100644 index 0000000..bcd3e51 --- /dev/null +++ b/tests/writer_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative '../test_helper' +require_relative '../lib/apt/spy2/writer' +require 'json' + +# test to check request lib +class WriterTest < Minitest::Test + def test_json + fixture = { 'mirror': 'http://example.org', 'status': 'up' } + + w = Apt::Spy2::Writer.new('json') + w.echo(fixture) + + # json string + j = w.to_json + + assert_equal(true, w.json?) + assert_equal(true, j.is_a?(String)) + assert_equal(true, JSON.parse(j).is_a?(Array)) + end + + def test_no_json + w = Apt::Spy2::Writer.new('shell') + assert_equal(false, w.json?) + end +end