Back to Repositories

Testing Process Management and Environment Configuration in ddollar/foreman

This test suite thoroughly validates the core functionality of Foreman’s Engine class, which handles process management and environment configuration. The tests cover process initialization, concurrent execution, directory handling, and environment variable management.

Test Coverage Overview

The test suite provides comprehensive coverage of Foreman::Engine’s critical functionalities.

Key areas tested include:
  • Process initialization and command reading from Procfile
  • Process forking and concurrent execution
  • Directory path handling
  • Environment variable loading and parsing
Edge cases covered include quoted values, multiline strings, and multiple environment file loading.

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development paradigm with extensive use of mocking and stubbing.

Notable patterns include:
  • Fake filesystem implementation using :fakefs
  • Custom test subclass (Foreman::Engine::Tester)
  • Nested describe blocks for logical grouping
  • Expectation-based assertions for process behavior

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • FakeFS for filesystem manipulation
  • Custom helper methods for file writing
  • Extensive use of before blocks for test setup
  • Mocking of process execution and output handling

Best Practices Demonstrated

The test suite exemplifies several testing best practices in Ruby.

Notable practices include:
  • Isolation of test cases using FakeFS
  • Proper test organization with context-specific describe blocks
  • Comprehensive edge case coverage
  • Clear test descriptions using RSpec’s declarative syntax
  • Effective use of subject blocks for DRY testing

ddollar/foreman

spec/foreman/engine_spec.rb

            
require "spec_helper"
require "foreman/engine"

class Foreman::Engine::Tester < Foreman::Engine
  attr_reader :buffer

  def startup
    @buffer = ""
  end

  def output(name, data)
    @buffer += "#{name}: #{data}"
  end

  def shutdown
  end
end

describe "Foreman::Engine", :fakefs do
  subject do
    write_procfile "Procfile"
    Foreman::Engine::Tester.new.load_procfile("Procfile")
  end

  describe "initialize" do
    describe "with a Procfile" do
      before { write_procfile }

      it "reads the processes" do
        expect(subject.process("alpha").command).to eq("./alpha")
        expect(subject.process("bravo").command).to eq("./bravo")
      end
    end
  end

  describe "start" do
    it "forks the processes" do
      expect(subject.process("alpha")).to receive(:run)
      expect(subject.process("bravo")).to receive(:run)
      expect(subject).to receive(:watch_for_output)
      expect(subject).to receive(:wait_for_shutdown_or_child_termination)
      subject.start
    end

    it "handles concurrency" do
      subject.options[:formation] = "alpha=2"
      expect(subject.process("alpha")).to receive(:run).twice
      expect(subject.process("bravo")).to_not receive(:run)
      expect(subject).to receive(:watch_for_output)
      expect(subject).to receive(:wait_for_shutdown_or_child_termination)
      subject.start
    end
  end

  describe "directories" do
    it "has the directory default relative to the Procfile" do
      write_procfile "/some/app/Procfile"
      engine = Foreman::Engine.new.load_procfile("/some/app/Procfile")
      expect(engine.root).to eq("/some/app")
    end
  end

  describe "environment" do
    it "should read env files" do
      write_file("/tmp/env") { |f| f.puts("FOO=baz") }
      subject.load_env("/tmp/env")
      expect(subject.env["FOO"]).to eq("baz")
    end

    it "should read more than one if specified" do
      write_file("/tmp/env1") { |f| f.puts("FOO=bar") }
      write_file("/tmp/env2") { |f| f.puts("BAZ=qux") }
      subject.load_env "/tmp/env1"
      subject.load_env "/tmp/env2"
      expect(subject.env["FOO"]).to eq("bar")
      expect(subject.env["BAZ"]).to eq("qux")
    end

    it "should handle quoted values" do
      write_file("/tmp/env") do |f|
        f.puts 'FOO=bar'
        f.puts 'BAZ="qux"'
        f.puts "FRED='barney'"
        f.puts 'OTHER="escaped\"quote"'
        f.puts 'URL="http://example.com/api?foo=bar&baz=1"'
      end
      subject.load_env "/tmp/env"
      expect(subject.env["FOO"]).to   eq("bar")
      expect(subject.env["BAZ"]).to   eq("qux")
      expect(subject.env["FRED"]).to  eq("barney")
      expect(subject.env["OTHER"]).to eq('escaped"quote')
      expect(subject.env["URL"]).to   eq("http://example.com/api?foo=bar&baz=1")
    end

    it "should handle multiline strings" do
      write_file("/tmp/env") do |f|
        f.puts 'FOO="bar\nbaz"'
      end
      subject.load_env "/tmp/env"
      expect(subject.env["FOO"]).to eq("bar\nbaz")
    end

    it "should fail if specified and doesnt exist" do
      expect { subject.load_env "/tmp/env" }.to raise_error(Errno::ENOENT)
    end

    it "should set port from .env if specified" do
      write_file("/tmp/env") { |f| f.puts("PORT=9000") }
      subject.load_env "/tmp/env"
      expect(subject.send(:base_port)).to eq(9000)
    end
  end

end