sparklemotion/nokogiri

View on GitHub
scripts/test-gem-file-contents

Summary

Maintainability
Test Coverage
#! /usr/bin/env ruby
# frozen_string_literal: true

#
#  this script is intended to run as part of the CI test suite.
#
#  it inspects the contents of a nokogiri gem file -- both the files and the gemspec -- to ensure
#  we're packaging what we expect, and that we're not packaging anything we don't expect.
#
#  this file isn't in the `test/` subdirectory because it's intended to be run standalone against a
#  built gem file (and not against the source code or behavior of the gem itself).
#

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "minitest"
end

require "yaml"

def usage_and_exit(message = nil)
  puts "ERROR: #{message}" if message
  puts "USAGE: #{File.basename(__FILE__)} <gemfile> [options]"
  exit(1)
end

usage_and_exit if ARGV.include?("-h")
usage_and_exit unless (gemfile = ARGV[0])
usage_and_exit("#{gemfile} does not exist") unless File.file?(gemfile)
usage_and_exit("#{gemfile} is not a gem") unless /\.gem$/.match?(gemfile)
gemfile = File.expand_path(gemfile)

gemfile_contents = Dir.mktmpdir do |dir|
  Dir.chdir(dir) do
    unless system("tar -xf #{gemfile} data.tar.gz")
      raise "could not unpack gem #{gemfile}"
    end

    %x(tar -ztf data.tar.gz).split("\n")
  end
end

gemspec = Dir.mktmpdir do |dir|
  Dir.chdir(dir) do
    unless system("tar -xf #{gemfile} metadata.gz")
      raise "could not unpack gem #{gemfile}"
    end

    YAML.safe_load(
      %x(gunzip -c metadata.gz),
      permitted_classes: [Gem::Specification, Gem::Version, Gem::Dependency, Gem::Requirement, Time, Symbol],
    )
  end
end

if ARGV.include?("-v")
  puts "---------- gemfile contents ----------"
  puts gemfile_contents
  puts
  puts "---------- gemspec ----------"
  puts gemspec.to_ruby
  puts
end

require "minitest/autorun"

puts "Testing '#{gemfile}' (#{gemspec.platform})"
describe File.basename(gemfile) do
  let(:cross_rubies_path) { File.join(File.dirname(__FILE__), "..", ".cross_rubies") }

  let(:platform_supported_ruby_versions) do
    File.read(cross_rubies_path).split("\n").filter_map do |line|
      ver, plat = line.split(":")
      next if plat != gemspec.platform.to_s

      ver.split(".").take(2).join(".") # ugh
    end.uniq.sort
  end

  let(:all_supported_ruby_versions) do
    File.read(cross_rubies_path).split("\n").map do |line|
      ver, _ = line.split(":")
      ver.split(".").take(2).join(".") # ugh
    end.uniq.sort
  end

  describe "setup" do
    it "gemfile contains some files" do
      actual = gemfile_contents.length
      assert_operator(actual, :>, 60, "expected gemfile to contain more than #{actual} files")
    end

    it "gemspec is a Gem::Specification" do
      assert_equal(Gem::Specification, gemspec.class)
    end
  end

  describe "all platforms" do
    it "contains every ruby file in lib/" do
      expected = %x(git ls-files lib).split("\n").grep(/\.rb$/).sort
      skip "looks like this isn't a git repository" if expected.empty?
      actual = gemfile_contents.grep(%r{^lib/}).grep(/\.rb$/).sort
      assert_equal(expected, actual)
    end
  end

  describe "ruby platform" do
    it "depends on mini_portile2" do
      assert(gemspec.dependencies.find { |d| d.name == "mini_portile2" })
    end

    it "contains ext/nokogiri C and header files" do
      assert_operator(gemfile_contents.grep(%r{^ext/nokogiri/.*\.c}).length, :>, 20)
      assert_operator(gemfile_contents.grep(%r{^ext/nokogiri/.*\.h}).length, :>, 0)
    end

    it "includes C files in extra_rdoc_files" do
      assert_operator(gemspec.extra_rdoc_files.grep(%r{ext/nokogiri/.*\.c$}).length, :>, 10)
    end

    it "contains the port files" do
      actual_ports = gemfile_contents.grep(%r{^ports/})
      assert_equal(
        1,
        actual_ports.grep(/libxml2-\d+\.\d+\.\d+\.tar\.[gx]z/).length,
        "expected #{actual_ports} to include libxml2",
      )
      assert_equal(
        1,
        actual_ports.grep(/libxslt-\d+\.\d+\.\d+\.tar\.[gx]z/).length,
        "expected #{actual_ports} to include libxslt",
      )
      assert_equal(2, actual_ports.length)
    end

    it "contains the patch files" do
      assert_operator(gemfile_contents.grep(%r{^patches/}).length, :>, 0)
    end

    it "does not contain packaged libraries' header files" do
      # these files are present after installation if the packaged libraries are used
      assert_empty(gemfile_contents.grep(%r{^ext/nokogiri/include/}))
    end

    it "contains the gumbo parser source code" do
      assert_includes(gemfile_contents, "gumbo-parser/src/Makefile")
      assert_operator(gemfile_contents.grep(%r{^gumbo-parser/src/.*\.c}).length, :>, 10)
      assert_operator(gemfile_contents.grep(%r{^gumbo-parser/src/.*\.h}).length, :>, 10)
    end

    it "does not contain java files" do
      assert_empty(gemfile_contents.grep(%r{^ext/java/}))
      assert_empty(gemfile_contents.grep(/.*\.jar$/))
    end
  end if gemspec.platform == Gem::Platform::RUBY

  describe "native platform" do
    it "does not depend on mini_portile2" do
      refute(gemspec.dependencies.find { |d| d.name == "mini_portile2" })
    end

    it "contains ext/nokogiri C and header files" do
      assert_operator(gemfile_contents.grep(%r{^ext/nokogiri/.*\.c}).length, :>, 20)
      assert_operator(gemfile_contents.grep(%r{^ext/nokogiri/.*\.h}).length, :>, 20)
    end

    it "includes C files in extra_rdoc_files" do
      assert_operator(gemspec.extra_rdoc_files.grep(%r{ext/nokogiri/.*\.c$}).length, :>, 10)
    end

    it "does not contain the port files" do
      assert_empty(gemfile_contents.grep(%r{^ports/}))
    end

    it "does not contain the patch files" do
      assert_empty(gemfile_contents.grep(%r{^patches/}))
    end

    it "contains packaged libraries' header files" do
      assert_includes(gemfile_contents, "ext/nokogiri/include/libxml2/libxml/tree.h")
      assert_includes(gemfile_contents, "ext/nokogiri/include/libxslt/xslt.h")
      assert_includes(gemfile_contents, "ext/nokogiri/include/libexslt/exslt.h")
    end

    it "does not contain the gumbo parser source code" do
      assert_empty(gemfile_contents.grep(%r{^gumbo-parser/src/}))
    end

    it "does not contain java files" do
      assert_empty(gemfile_contents.grep(%r{^ext/java/}))
      assert_empty(gemfile_contents.grep(/.*\.jar$/))
    end

    it "contains expected shared library files " do
      platform_supported_ruby_versions.each do |version|
        actual = gemfile_contents.find do |p|
          File.fnmatch?("lib/nokogiri/#{version}/nokogiri.{so,bundle}", p, File::FNM_EXTGLOB)
        end
        assert(actual, "expected to find shared library file for ruby #{version}")
      end

      actual = gemfile_contents.find do |p|
        File.fnmatch?("lib/nokogiri/nokogiri.{so,bundle}", p, File::FNM_EXTGLOB)
      end
      refute(actual, "did not expect to find shared library file in lib/nokogiri")

      actual = gemfile_contents.find_all do |p|
        File.fnmatch?("lib/nokogiri/**/*.{so,bundle}", p, File::FNM_EXTGLOB)
      end
      assert_equal(
        platform_supported_ruby_versions.length,
        actual.length,
        "did not expect extra shared library files",
      )
    end

    it "sets required_ruby_version appropriately" do
      unsupported_versions = all_supported_ruby_versions - platform_supported_ruby_versions
      platform_supported_ruby_versions.each do |v|
        assert(
          gemspec.required_ruby_version.satisfied_by?(Gem::Version.new(v)),
          "required_ruby_version='#{gemspec.required_ruby_version}' should support ruby #{v}",
        )
      end
      unsupported_versions.each do |v|
        refute(
          gemspec.required_ruby_version.satisfied_by?(Gem::Version.new(v)),
          "required_ruby_version='#{gemspec.required_ruby_version}' should not support ruby #{v}",
        )
      end
    end
  end if gemspec.platform.is_a?(Gem::Platform) && gemspec.platform.cpu

  describe "java platform" do
    it "does not depend on mini_portile2" do
      refute(gemspec.dependencies.find { |d| d.name == "mini_portile2" })
    end

    it "contains ext/nokogiri C files" do
      # NOTE: we keep the C files because they have docstrings and Java files don't
      assert_operator(gemfile_contents.grep(%r{^ext/nokogiri/.*\.c}).length, :>, 20)
    end

    it "does not contain ext/nokogiri header files" do
      assert_empty(gemfile_contents.grep(%r{^ext/nokogiri/.*\.h}))
    end

    it "includes C files in extra_rdoc_files" do
      assert_operator(gemspec.extra_rdoc_files.grep(%r{ext/nokogiri/.*\.c$}).length, :>, 10)
    end

    it "does not contain the port files" do
      assert_empty(gemfile_contents.grep(%r{^ports/}))
    end

    it "does not contain the patch files" do
      assert_empty(gemfile_contents.grep(%r{^patches/}))
    end

    it "does not contain packaged libraries' header files" do
      assert_empty(gemfile_contents.grep(%r{^ext/nokogiri/include/}))
    end

    it "does not contain the gumbo parser source code" do
      assert_empty(gemfile_contents.grep(%r{^gumbo-parser/src/}))
    end

    it "contains java source files" do
      assert_operator(gemfile_contents.grep(%r{^ext/java/.*\.java}).length, :>, 20)
    end

    it "contains the java jar files" do
      assert_includes(gemfile_contents, "lib/nokogiri/nokogiri.jar")

      actual_jars = gemfile_contents.grep(/.*\.jar$/)
      expected_jars = [
        "isorelax",
        "jing",
        "neko-htmlunit",
        "nekodtd",
        "serializer",
        "xalan",
        "xercesImpl",
        "xml-apis",
      ]
      expected_jars.each do |name|
        assert_equal(
          1,
          actual_jars.count { |j| File.fnmatch?("lib/nokogiri/jruby/**/#{name}-*.jar", j) },
          "expected to contain '#{name}-*.jar'",
        )
      end
    end
  end if gemspec.platform == Gem::Platform.new("java")
end