Skip to content

Commit

Permalink
Add tests and CI (#375)
Browse files Browse the repository at this point in the history
* Add tests and CI

Deploying with Capistrano and systemd can be difficult to verify manually, so I automated the verification using tests and CI. I used a Debian Docker image to create a container running systemd, and deployed a small Sinatra application to confirm it works.

With mostly default settings, I confirmed that the four tasks puma:install, deploy, puma:stop, and puma:start work as expected. It would be good to also verify other tasks and the nginx configuration, but for now, this provides the minimum necessary verification.

* Fix cap production puma:install due to loginctl need sudo

```
#<Thread:0x000000012a5d7570 /Users/willnet/.rbenv/versions/3.3.3/lib/ruby/gems/3.3.0/gems/sshkit-1.22.2/lib/sshkit/runners/parallel.rb:10 run> terminated with exception (report_on_exception is true):
/Users/willnet/.rbenv/versions/3.3.3/lib/ruby/gems/3.3.0/gems/sshkit-1.22.2/lib/sshkit/runners/parallel.rb:15:in `rescue in block (2 levels) in execute': Exception while executing as willnet@localhost: Exception while executing as willnet@localhost: loginctl exit status: 1 (SSHKit::Runner::ExecuteError)
loginctl stdout: Nothing written
loginctl stderr: Could not enable linger: Access denied

	from /Users/willnet/.rbenv/versions/3.3.3/lib/ruby/gems/3.3.0/gems/sshkit-1.22.2/lib/sshkit/runners/parallel.rb:11:in `block (2 levels) in execute'
/Users/willnet/.rbenv/versions/3.3.3/lib/ruby/gems/3.3.0/gems/sshkit-1.22.2/lib/sshkit/runners/parallel.rb:15:in `rescue in block (2 levels) in execute': Exception while executing as willnet@localhost: loginctl exit status: 1 (SSHKit::Runner::ExecuteError)
loginctl stdout: Nothing written
loginctl stderr: Could not enable linger: Access denied

	from /Users/willnet/.rbenv/versions/3.3.3/lib/ruby/gems/3.3.0/gems/sshkit-1.22.2/lib/sshkit/runners/parallel.rb:11:in `block (2 levels) in execute'
/Users/willnet/.rbenv/versions/3.3.3/lib/ruby/gems/3.3.0/gems/sshkit-1.22.2/lib/sshkit/command.rb:97:in `exit_status=': loginctl exit status: 1 (SSHKit::Command::Failed)
loginctl stdout: Nothing written
loginctl stderr: Could not enable linger: Access denied
```

* Fix ci on Ruby 2.5

Fix an issue where the argument passing method for ERB.new in Ruby 2.5 differs from Ruby 2.6 and above, causing errors during template generation.

```
SSHKit::Runner::ExecuteError: Exception while executing as willnet@localhost: no implicit conversion of Hash into Integer
```

ref: https://ruby-doc.org/stdlib-2.5.0/libdoc/erb/rdoc/ERB.html#method-c-new
  • Loading branch information
willnet authored Aug 7, 2024
1 parent 2cc1184 commit ae98407
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 2 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Run Test

on:
push:
branches:
- master
pull_request:

jobs:
rake:
runs-on: ubuntu-latest

strategy:
matrix:
ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3']

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- name: Run Rake Task
run: bundle exec rake

- name: Upload Capistrano log
if: always()
uses: actions/upload-artifact@v4
with:
name: capistrano-log-${{ matrix.ruby-version }}
path: test/log/*.log
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.idea
.yardoc
Gemfile.lock
!test/app/Gemfile.lock
InstalledFiles
_yardoc
coverage
Expand All @@ -15,5 +16,7 @@ rdoc
spec/reports
test/tmp
test/version_tmp
test/log/*
!test/log/.gitkeep
tmp
*.iml
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in capistrano-puma.gemspec
gemspec

gem 'minitest'

10 changes: 10 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
require "bundler/gem_tasks"

require 'rake/testtask'

Rake::TestTask.new do |t|
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end

task default: :test
6 changes: 5 additions & 1 deletion lib/capistrano/puma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ def compiled_template_puma(from, role)
File.expand_path("../templates/#{from}.rb.erb", __FILE__)
].detect { |path| File.file?(path) }
erb = File.read(file)
StringIO.new(ERB.new(erb, trim_mode: '-').result(binding))
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.6')
StringIO.new(ERB.new(erb, nil, '-').result(binding))
else
StringIO.new(ERB.new(erb, trim_mode: '-').result(binding))
end
end

def template_puma(from, to, role)
Expand Down
2 changes: 1 addition & 1 deletion lib/capistrano/tasks/systemd.rake
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ namespace :puma do
git_plugin.execute_systemd("enable", fetch(:puma_service_unit_name) + ".socket") if fetch(:puma_enable_socket_service)

if fetch(:puma_systemctl_user) != :system && fetch(:puma_enable_lingering)
execute :loginctl, "enable-linger", fetch(:puma_lingering_user)
sudo "loginctl enable-linger #{fetch(:puma_lingering_user)}"
end
end
end
Expand Down
40 changes: 40 additions & 0 deletions test/Capfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
# https://github.com/capistrano/rvm
# https://github.com/capistrano/rbenv
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails
# https://github.com/capistrano/passenger
#
# require "capistrano/rvm"
# require "capistrano/rbenv"
# require "capistrano/chruby"
# require "capistrano/bundler"
# require "capistrano/rails/assets"
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/puma'
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Systemd
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
14 changes: 14 additions & 0 deletions test/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM ruby:3.3

RUN apt-get update && apt-get install -y systemd systemd-sysv openssh-server sudo && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN groupadd --system --gid 1000 willnet && \
useradd willnet --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R willnet:willnet /var
RUN echo "willnet ALL=(ALL) NOPASSWD:ALL" | tee /etc/sudoers.d/willnet
RUN echo 'willnet:password' | chpasswd

EXPOSE 22
CMD ["/lib/systemd/systemd"]
6 changes: 6 additions & 0 deletions test/app/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "sinatra", "~> 4.0"
gem "puma", "~> 6.4"
34 changes: 34 additions & 0 deletions test/app/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
GEM
remote: https://rubygems.org/
specs:
base64 (0.2.0)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
nio4r (2.7.3)
puma (6.4.2)
nio4r (~> 2.0)
rack (3.1.3)
rack-protection (4.0.0)
base64 (>= 0.1.0)
rack (>= 3.0.0, < 4)
rack-session (2.0.0)
rack (>= 3.0.0)
ruby2_keywords (0.0.5)
sinatra (4.0.0)
mustermann (~> 3.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.0.0)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
tilt (2.3.0)

PLATFORMS
arm64-darwin-23
ruby

DEPENDENCIES
puma (~> 6.4)
sinatra (~> 4.0)

BUNDLED WITH
2.5.11
7 changes: 7 additions & 0 deletions test/app/config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'sinatra'

get '/' do
'Hello, Puma'
end

run Sinatra::Application
1 change: 1 addition & 0 deletions test/app/config/puma.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
port 3000
10 changes: 10 additions & 0 deletions test/config/deploy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
lock "~> 3.19.1"

set :application, "capistrano-puma"
set :repo_url, "https://github.com/seuros/capistrano-puma.git"
set :repo_tree, 'test/app'

set :branch, ENV['BRANCH'] || 'master'

# Without the following settings, puma will fail to start.
append :linked_dirs, "log"
8 changes: 8 additions & 0 deletions test/config/deploy/production.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
server "localhost", user: "willnet", roles: %w{app db web}, port: 8022

set :ssh_options, {
user: 'willnet',
password: 'password',
forward_agent: false,
auth_methods: %w(password)
}
51 changes: 51 additions & 0 deletions test/deploy_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
require 'minitest/autorun'
require 'net/http'

Minitest.after_run do
DeployTest.container_id && system("docker stop #{DeployTest.container_id}")
end

class DeployTest < Minitest::Test
class << self
attr_accessor :container_id
end

def self.before_suite
system 'docker build -t capistrano-puma-test-server test'
self.container_id = `docker run -d --privileged -p 8022:22 -p 3000:3000 capistrano-puma-test-server`
sleep 1
end

before_suite

def retry_get_response(uri, limit = 5)
response = nil
limit.times do
begin
response = Net::HTTP.get_response(URI.parse(uri))
rescue Errno::ECONNRESET, EOFError
sleep 1
else
break
end
end
response
end

def test_deploy
Dir.chdir('test') do
system 'cap production puma:install'
system 'cap production deploy'
response = retry_get_response('http://localhost:3000')
assert_equal '200', response.code
system 'cap production puma:stop'
sleep 1
assert_raises(Errno::ECONNRESET) do
Net::HTTP.get_response(URI.parse('http://localhost:3000'))
end
system 'cap production puma:start'
response = retry_get_response('http://localhost:3000')
assert_equal '200', response.code
end
end
end
Empty file added test/log/.gitkeep
Empty file.

0 comments on commit ae98407

Please sign in to comment.