Back to Repositories

Validating Directory Package Management Operations in FPM

This test suite validates directory packaging functionality in FPM (Effing Package Management), focusing on file copying, path mapping, and symlink handling behaviors. The tests ensure proper directory structure preservation and attribute handling during package creation.

Test Coverage Overview

The test suite provides comprehensive coverage of FPM’s directory packaging capabilities:

  • Single file copying to output root
  • Full path preservation during copying
  • Directory structure replication
  • Prefix attribute handling
  • Path mapping with various input patterns
  • Symlink handling and preservation

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development framework with temporary directory manipulation:

Tests are organized using nested contexts and employ let blocks for setup. The implementation leverages Ruby’s FileUtils and custom temporary directory management through Stud::Temporary to ensure isolated test environments.

Technical Details

Key technical components include:

  • RSpec for test framework
  • Stud::Temporary for directory management
  • FileUtils for file operations
  • SecureRandom for UUID generation (Ruby 1.8 compatibility)
  • Custom assertions using ‘insist’ syntax

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation through temporary directories
  • Consistent cleanup after each test
  • Comprehensive edge case coverage
  • Clear test descriptions
  • Modular test organization using contexts

jordansissel/fpm

spec/fpm/package/dir_spec.rb

            
require "spec_setup"
require "fpm" # local
require "fpm/package/dir" # local
require "stud/temporary"

if RUBY_VERSION =~ /^1\.8/
  # The following method copied from ruby 1.9.3
  module SecureRandom
    def self.uuid
      ary = self.random_bytes(16).unpack("NnnnnN")
      ary[2] = (ary[2] & 0x0fff) | 0x4000
      ary[3] = (ary[3] & 0x3fff) | 0x8000
      "%08x-%04x-%04x-%04x-%04x%08x" % ary
    end
  end
end

describe FPM::Package::Dir do
  let(:tmpdir) { Stud::Temporary.directory("tmpdir") }
  let(:output) { Stud::Temporary.directory("output") }
  #let(:tmpdir) { ::Dir.mkdir("/tmp/tmpdir"); "/tmp/tmpdir" }
  #let(:output) { ::Dir.mkdir("/tmp/output"); "/tmp/output" }

  after :each do
    subject.cleanup
    FileUtils.rm_r(tmpdir)
    FileUtils.rm_r(output)
  end # after

  it "single file: should copy single files to the root of the output" do
    hello_in = File.join(tmpdir, "hello")
    hello_out = File.join(output, tmpdir, "hello")
    file = File.write(hello_in, "Hello world")
    subject.input(tmpdir)
    subject.output(output)
    insist { File.read(hello_out) } == File.read(hello_in)
  end

  it "should copy the full path given (single file)" do
    dir = File.join(tmpdir, "a", "b", "c")
    FileUtils.mkdir_p(dir)
    hello_in = File.join(dir, "hello")
    hello_out = File.join(output, dir, "hello")
    File.write(hello_in, "Hello world")
    subject.input(tmpdir)
    subject.output(output)
    insist { File.read(hello_out) } == File.read(hello_in)
  end

  it "should copy entire directories given as input" do
    dir = File.join(tmpdir, "a", "b", "c")
    FileUtils.mkdir_p(dir)
    files = rand(50).times.collect do |i|
      file = File.join(dir, "hello-#{i}")
      File.write(file, rand(1000))
      next file
    end

    subject.input(tmpdir)
    subject.output(output)

    files.each do |file|
      insist { File.read(File.join(output, file)) } == File.read(file)
    end
  end

  it "should obey the :prefix attribute" do
    prefix = subject.attributes[:prefix] = "/usr/local"
    file = File.join(tmpdir, "hello")
    File.write(file, "Hello world")
    subject.input(tmpdir)
    subject.output(output)

    expected_path = File.join(".", file)
    path = File.join(prefix, expected_path)
    insist { File.read(File.join(output, path)) } == File.read(file)
  end

  it "should obey :chdir and :prefix attributes together" do
    prefix = subject.attributes[:prefix] = "/usr/local"
    chdir = subject.attributes[:chdir] = tmpdir
    file = File.join(tmpdir, "hello")
    File.write(file, "Hello world")
    subject.input(".") # since we chdir, copy the entire root
    subject.output(output)

    # path relative to the @output directory.
    expected_path = File.join(".", prefix, File.basename(file))
    insist { File.read(File.join(output, expected_path)) } == File.read(file)
  end

  context "path mapping" do
    # 'rsync -a' semantics
    mappings = {
      # this input    => should produce this file
      "/a/b=/example" => "./example",
      "/a/b=/example/" => "./example/b",
      "/a=/example/" => "./example/a/b",
      "/a=/example" => "./example/a/b",
      "/a/=/example/" => "./example/b"
    }

    mappings.each do |input, expected_file|
      it "should take #{input} and produce #{expected_file}" do
        Dir.mkdir(File.join(tmpdir, "a"))
        File.write(File.join(tmpdir, "a", "b"), "hello world")
        subject.input(File.join(tmpdir, input))
        subject.output(output)
        insist { File }.exist?(File.join(output, expected_file))
      end
    end

    it "should not map existing paths with = in them" do
      File.write(File.join(tmpdir, "a=b"), "hello world")
      subject.input(File.join(tmpdir, "a=b"))
      subject.output(output)
      insist { File }.exist?(File.join(output, tmpdir, "a=b"))
    end

    it "should not map existing paths with = in them and obey :chdir and :prefix attributes" do
      Dir.mkdir(File.join(tmpdir, "a"))
      File.write(File.join(tmpdir,"a",  "a=b"), "hello world")
      subject.attributes[:chdir] = tmpdir
      subject.attributes[:prefix] = "/foo"
      subject.input(File.join("a", "a=b"))
      subject.output(output)
      insist { File }.exist?(File.join(output, "foo", "a", "a=b"))
    end
  end

  context "SYMLINKS." do
    let(:path) { Stud::Temporary.pathname }
    let(:broken_target) { File.join("no", "such", "path", "here", rand(1000).to_s, rand(1000).to_s) }
    before do
      File.symlink(broken_target, path)
    end
    after do
      File.unlink(path)
    end
    it "should copy a broken symlink because it shouldn't be following symlinks to begin with" do
      subject.input(path)
    end
  end

  context "symlink=dest_symlink." do
    it "Should not put the symlink into directory" do
      filepath = File.join(tmpdir, "target")
      File.write(filepath, "hello!");
      symlinkpath = File.join(tmpdir, "properlink.so")
      File.symlink(filepath, symlinkpath);

      subject.input(symlinkpath + "=" + "/a/b/properlink.so")
      subject.output(output)
      insist { File.read(File.join(output, "/a/b/properlink.so")) } == "hello!"
    end
  end

  context "symlink=dest_directory/" do
    it "Should allow source=destination/ for symlinks" do
      filepath = File.join(tmpdir, "target")
      File.write(filepath, "hello!");
      symlinkpath = File.join(tmpdir, "properlink.so")
      File.symlink(filepath, symlinkpath);

      subject.input(symlinkpath + "=" + "/a/b/")
      subject.output(output)
      insist { File.read(File.join(output, "/a/b/properlink.so")) } == "hello!"
    end
  end

  context "symlink=dest_dir/" do
    it "Should put the symlink into directory with link syntax" do
      filepath = File.join(tmpdir, "target")
      File.write(filepath, "hello!");
      symlinkpath = File.join(tmpdir, "link.so")
      File.symlink(filepath, symlinkpath);

      subject.input(symlinkpath)
      subject.output(output)
      insist { File.read(File.join(output, symlinkpath)) } == "hello!"
    end
  end
end # describe FPM::Package::Dir