Back to Repositories

Testing Pacman Package Format Implementation in FPM

This test suite validates the Pacman package format functionality in FPM (Effing Package Management), focusing on package creation, attribute handling, and file permission preservation. The tests ensure proper conversion between different package formats and validate core Pacman package specifications.

Test Coverage Overview

The test suite provides comprehensive coverage of Pacman package operations including:

  • Architecture conversion and validation
  • Package naming and versioning conventions
  • Dependency management and verification
  • File permission preservation
  • Package metadata handling

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development framework to systematically verify Pacman package functionality. Tests employ before/after hooks for setup and cleanup, context blocks for different scenarios, and extensive use of let blocks for test isolation.

Key patterns include:
  • Fixture-based testing with temporary file generation
  • Dependency injection for package attributes
  • Comprehensive script verification

Technical Details

Testing tools and configuration:

  • RSpec for test framework
  • Stud::Temporary for temporary file handling
  • bsdtar and zstd as required dependencies
  • FileUtils for file system operations
  • Custom helper methods for program existence verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation and cleanup
  • Comprehensive error handling and dependency checking
  • Detailed permission and attribute verification
  • Modular test organization with nested describe blocks
  • Clear test descriptions and failure messages

jordansissel/fpm

spec/fpm/package/pacman_spec.rb

            
require "spec_setup"
require 'fileutils'
require "fpm" # local
require "fpm/package/pacman" # local
require "stud/temporary"


if !program_exists?("bsdtar")
  Cabin::Channel.get("rspec") \
    .warn("Skipping Pacman tests because 'bsdtar' isn't in your PATH")
end

if !program_exists?("zstd")
  Cabin::Channel.get("rspec") \
    .warn("Skipping Pacman tests because 'zstd' isn't in your PATH")
end

def skip?
  missing = []
  missing << "bsdtar" unless program_exists?("bsdtar")
  missing << "zstd" unless program_exists?("zstd")
  return nil if missing.empty?
  return "Missing programs: #{missing.join(", ")}"
end

describe FPM::Package::Pacman do
  let(:target) { Stud::Temporary.pathname + ".pkg.tar.zst" }
  after do
    subject.cleanup
    File.unlink(target) if File.exist?(target)
  end

  describe "#architecture" do
    it "should convert amd64 to x86_64" do
      subject.architecture = "amd64"
      expect(subject.architecture).to(be == "x86_64")
    end

    it "should convert noarch to any" do
      subject.architecture = "noarch"
      expect(subject.architecture).to(be == "any")
    end

    let(:native) { `uname -m`.chomp }

    it "should default to native" do
      # Convert kernel name to debian name
      expect(subject.architecture).to(be == native)
    end
  end

  describe "#iteration" do
    it "should default to 1" do
      expect(subject.iteration).to(be == 1)
    end
  end

  describe "#epoch" do
    it "should default to nil" do
      expect(subject.epoch).to(be_nil)
    end
  end

  describe "#optional_depends" do
    it "should default to []" do
      expect(subject.attributes[:pacman_optional_depends]).to(be == [])
    end
  end

  describe "#to_s" do
    before do
      subject.name = "name"
      subject.version = "123"
      subject.architecture = "any"
      subject.iteration = "100"
      subject.epoch = "5"
    end

    it "should have a default output usable as a filename" do
      # This is the default filename I see commonly produced by debuild
      insist { subject.to_s } == "name-123-100-any.pkg.tar.zst"
    end

    context "when iteration is nil" do
      before do
        subject.iteration = nil
      end

      it "should have an iteration of `1`" do
        # This is the default filename I see commonly produced by debuild
        expect(subject.to_s).to(be == "name-123-1-any.pkg.tar.zst")
      end
    end
  end

  describe "#output" do
    let(:original) { FPM::Package::Pacman.new }
    let(:input) { FPM::Package::Pacman.new }

    context "Test that empty epoch is tested properly" do
      before do
        skip(skip?) if skip?

        original.name = "foo"
        original.version = "123"
        original.iteration = "100"
        # original.epoch conspicuously absent
        original.architecture = "all"
        original.output(target)
        input.input(target)
      end
      it "should have the correct name" do
        skip("Missing bsdtar") unless program_exists?("bsdtar")
        skip("Missing zstd") unless program_exists?("zstd")
        insist { input.name } == original.name
      end

      it "should have the correct version" do
        skip("Missing bsdtar") unless program_exists?("bsdtar")
        skip("Missing zstd") unless program_exists?("zstd")
        insist { input.version } == original.version
      end

      it "should have the correct iteration" do
        skip("Missing bsdtar") unless program_exists?("bsdtar")
        skip("Missing zstd") unless program_exists?("zstd")
        insist { input.iteration } == original.iteration
      end

      it "should have the correct epoch" do
        skip("Missing bsdtar") unless program_exists?("bsdtar")
        skip("Missing zstd") unless program_exists?("zstd")
        insist { input.epoch } == original.epoch
      end
    end


    context "normal tests" do
      before do
        skip(skip?) if skip?
        
        # output a package, use it as the input, set the subject to that input
        # package. This helps ensure that we can write and read packages
        # properly.
        # The target file must not exist.

        original.name = "foo"
        original.version = "123"
        original.iteration = "100"
        original.epoch = "5"
        original.architecture = "all"
        original.dependencies << "hello >= 20"
        original.dependencies << "something > 10"
        original.dependencies << "rpmlib(bogus)"
        original.dependencies << "/usr/bin/bad-dep"
        original.provides << "#{original.name} = #{original.version}"

        original.conflicts = ["foo < 123"]
        original.attributes[:pacman_opt_depends] = ["bamb > 10"]
        original.directories << '/var/lib/foo'

        ::Dir.chdir(original.staging_path) do
          FileUtils::mkdir_p 'usr/bin'
          File.open('usr/bin/foo', 'w') do |exe|
            exe.write("Frankly, I think the odds are slightly in your favor " \
                      "at hand fighting.")
          end
          File.chmod(0755, 'usr')
          File.chmod(0755, 'usr/bin')
          File.chmod(0755, 'usr/bin/foo')
          FileUtils::mkdir_p 'usr/share/doc'
          File.chmod(0755, 'usr/share')
          File.chmod(0755, 'usr/share/doc')
          File.open('usr/share/doc/foo.txt', 'w') do |doc|
            doc.write("It's not my fault I'm the biggest or the strongest.")
          end
          File.chmod(0644, 'usr/share/doc/foo.txt')
          FileUtils::mkdir_p 'usr/lib'
          # incorrectly permissioned path (but that's what these tests are for)
          File.chmod(0700, 'usr/lib')
          File.open('usr/lib/libfoo.so', 'w') do |lib|
            lib.write("I don't even excercise.")
          end
          File.chmod(0755, 'usr/lib/libfoo.so')
          FileUtils::mkdir_p 'etc'
          File.chmod(0755, 'etc')
          File.open('etc/foo.conf', 'w') do |conf|
            conf.write("You mean, you'll put down your rock and I'll put down " \
                       "my sword, and we'll try and kill each other like " \
                       "civilized people?")
          end
          File.chmod(0600, 'etc/foo.conf')
          FileUtils::mkdir_p 'var/lib/foo'
          File.chmod(0755, 'var')
          File.chmod(0755, 'var/lib')
          File.chmod(0755, 'var/lib/foo')
        end

        [:before_install, :after_install, :before_remove, :after_remove,
         :before_upgrade, :after_upgrade].each do |script|
          original.scripts[script] = "#!/bin/sh\n\necho #{script.to_s}"
        end

        original.output(target)
        input.input(target)
      end

      after do
        original.cleanup
        input.cleanup
      end # after

      context "script contents" do
        [:before_install, :after_install, :before_remove, :after_remove,
         :before_upgrade, :after_upgrade].each do |script|
          it "should be the same both with input as with original for #{script.to_s}" do
            skip("Missing bsdtar") unless program_exists?("bsdtar")
            skip("Missing zstd") unless program_exists?("zstd")
            expect( \
                   (input.scripts[script] =~ \
                    /[\n :]+#{Regexp.quote(original.scripts[script])}/m \
                   )
                  ).to(be_truthy)
          end
        end
      end

      context "file permissions" do
        {"/usr" => 0755,
         "/usr/bin" => 0755,
         "/usr/bin/foo" => 0755,
         "/usr/lib" => 0700,
         "/usr/lib/libfoo.so" => 0755,
         "/usr/share" => 0755,
         "/usr/share/doc" => 0755,
         "/usr/share/doc/foo.txt" => 0644,
         "/etc" => 0755,
         "/etc/foo.conf" => 0600,
         "/var" => 0755,
         "/var/lib" => 0755,
         "/var/lib/foo" => 0755}.each do |dir, perm|
           it "should preserve file permissions for #{dir}" do
             skip("Missing bsdtar") unless program_exists?("bsdtar")
             skip("Missing zstd") unless program_exists?("zstd")
             insist { File.stat(File.join(input.staging_path,
                                          dir)).mode & 07777 } == perm
           end
         end
      end

      context "package attributes" do
        it "should have the correct name" do
          skip("Missing bsdtar") unless program_exists?("bsdtar")
          skip("Missing zstd") unless program_exists?("zstd")
          insist { input.name } == original.name
        end

        it "should have the correct version" do
          skip("Missing bsdtar") unless program_exists?("bsdtar")
          skip("Missing zstd") unless program_exists?("zstd")
          insist { input.version } == original.version
        end

        it "should have the correct iteration" do
          skip("Missing bsdtar") unless program_exists?("bsdtar")
          skip("Missing zstd") unless program_exists?("zstd")
          insist { input.iteration } == original.iteration
        end

        it "should have the correct epoch" do
          skip("Missing bsdtar") unless program_exists?("bsdtar")
          skip("Missing zstd") unless program_exists?("zstd")
          insist { input.epoch } == original.epoch
        end

        it "should not have bogus dependencies, just correct dependencies" do
          expect(input.dependencies).to(be == ["hello >= 20", "something > 10"])
        end
      end # package attributes
    end # #output
  end
  # TODO: output sometimes make fu-:1.2.3.out.rpm or something. Make sure the
  # version isn't screwed up in transit.
end # describe FPM::Package::Pacman