Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

### UNRELEASED

### 8.3.0

* Revert to 8.1.3 because of [the issue #301](https://github.com/KnapsackPro/knapsack_pro-ruby/issues/301)

https://github.com/KnapsackPro/knapsack_pro-ruby/pull/302

https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v8.2.0...v8.3.0

### 8.2.0

* Avoid needing a shell to support Distroless Container Images (in most cases)
Expand Down
36 changes: 0 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,42 +72,6 @@ bin/test

Scripted tests can be found in the [Rails App With Knapsack Pro repository](https://github.com/KnapsackPro/rails-app-with-knapsack_pro/blob/master/bin/knapsack_pro_all.rb).

### Distroless Container Images

Ensure that Knapsack Pro is written in a way that supports Distroless Container Images. Avoid using a shell when calling methods like [`Kernel.system`](https://rubyapi.org/3.4/o/kernel#method-i-system) or [`Kernel.exec`](https://rubyapi.org/3.4/o/kernel#method-i-exec).

✅ Good - shell is not required

```ruby
cmd = 'bundle exec rake knapsack_pro:something'
Kernel.system({ 'RAILS_ENV' => 'test' }, cmd)

cmd = ['bundle', 'exec', 'rake', 'knapsack_pro:example']
Kernel.system({ 'RAILS_ENV' => 'test' }, *cmd)
```

⛔️ Bad - shell is required

```ruby
# Avoid embedding environment variables in the command string
cmd = 'RAILS_ENV=test bundle exec rake knapsack_pro:something'
Kernel.system(cmd)

# Avoid output redirection
cmd = 'program 2>/dev/null'
Kernel.system(cmd)

# Avoid using the pipe operator
cmd = 'program1 | program2'
Kernel.system(cmd)
```

Use [Dockerfile](distroless/Dockerfile) to test if the code requires a shell:

```bash
docker build -t test -f distroless/Dockerfile . && docker run --rm -it test
```

### Publishing

1. Move the changes listed in the `UNRELEASED` section of the `CHANGELOG.md` to the proper version
Expand Down
37 changes: 0 additions & 37 deletions distroless/Dockerfile

This file was deleted.

10 changes: 5 additions & 5 deletions lib/knapsack_pro/adapters/rspec_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ def self.test_file_cases_for(slow_test_files)
KnapsackPro.logger.info("Generating RSpec test examples JSON report for slow test files to prepare it to be split by test examples (by individual test cases). Thanks to that, a single slow test file can be split across parallel CI nodes. Analyzing #{slow_test_files.size} slow test files.")

# generate the RSpec JSON report in a separate process to not pollute the RSpec state
envs = {'RACK_ENV' => 'test', 'RAILS_ENV' => 'test'}
cmd = [
'RACK_ENV=test',
'RAILS_ENV=test',
KnapsackPro::Config::Env.rspec_test_example_detector_prefix,
'rake knapsack_pro:rspec_test_example_detector'
'rake knapsack_pro:rspec_test_example_detector',
].join(' ')
unless Kernel.system(envs, cmd)
inline_cmd = envs.map { _1.join('=') }.join(' ') + ' ' + cmd
raise "Could not generate JSON report for RSpec. Rake task failed when running #{inline_cmd}"
unless Kernel.system(cmd)
raise "Could not generate JSON report for RSpec. Rake task failed when running #{cmd}"
end

# read the JSON report
Expand Down
27 changes: 6 additions & 21 deletions lib/knapsack_pro/repository_adapters/git_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,41 +44,26 @@ def build_author

def git_commit_authors
if KnapsackPro::Config::Env.ci? && shallow_repository?
command = 'git fetch --shallow-since "one month ago" --quiet'
command = 'git fetch --shallow-since "one month ago" --quiet 2>/dev/null'
begin
Timeout.timeout(5) do
Kernel.system(command, out: File::NULL, err: File::NULL)
`#{command}`
end
rescue Timeout::Error
KnapsackPro.logger.debug("Skip the `#{command}` command because it took too long.")
end
end

IO.pipe do |log_r, log_w|
Kernel.system('git log --since "one month ago"', out: log_w, err: File::NULL)
log_w.close
IO.pipe do |shortlog_r, shortlog_w|
Kernel.system('git shortlog --summary --email', in: log_r, out: shortlog_w, err: File::NULL)
shortlog_w.close
shortlog_r.read
end
end
`git log --since "one month ago" 2>/dev/null | git shortlog --summary --email 2>/dev/null`
end

def git_build_author
IO.pipe do |r, w|
Kernel.system('git log --format="%aN <%aE>" -1', out: w, err: File::NULL)
w.close
r.read
end
`git log --format="%aN <%aE>" -1 2>/dev/null`
end

def shallow_repository?
IO.pipe do |r, w|
Kernel.system('git rev-parse --is-shallow-repository', out: w, err: File::NULL)
w.close
r.read.strip == 'true'
end
result = `git rev-parse --is-shallow-repository 2>/dev/null`
result.strip == 'true'
end

def working_dir
Expand Down
7 changes: 2 additions & 5 deletions lib/knapsack_pro/runners/spinach_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ def self.run(args)

KnapsackPro.tracker.set_prerun_tests(runner.test_file_paths)

cmd = %Q[bundle exec spinach #{args} --features_path #{runner.test_dir} -- #{runner.stringify_test_file_paths}]
cmd = %Q[KNAPSACK_PRO_REGULAR_MODE_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=#{ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']} bundle exec spinach #{args} --features_path #{runner.test_dir} -- #{runner.stringify_test_file_paths}]

Kernel.exec({
'KNAPSACK_PRO_REGULAR_MODE_ENABLED' => 'true',
'KNAPSACK_PRO_TEST_SUITE_TOKEN' => ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']
}, cmd)
Kernel.exec(cmd)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/queue/cucumber.rake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require 'knapsack_pro'
namespace :knapsack_pro do
namespace :queue do
task :cucumber, [:cucumber_args] do |_, args|
Kernel.exec({ 'RAILS_ENV' => 'test', 'RACK_ENV' => 'test' }, $PROGRAM_NAME, "knapsack_pro:queue:cucumber_go[#{args[:cucumber_args]}]")
Kernel.exec("RAILS_ENV=test RACK_ENV=test #{$PROGRAM_NAME} 'knapsack_pro:queue:cucumber_go[#{args[:cucumber_args]}]'")
end

task :cucumber_go, [:cucumber_args] do |_, args|
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/queue/minitest.rake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require 'knapsack_pro'
namespace :knapsack_pro do
namespace :queue do
task :minitest, [:minitest_args] do |_, args|
Kernel.exec({ 'RAILS_ENV' => 'test', 'RACK_ENV' => 'test' }, $PROGRAM_NAME, "knapsack_pro:queue:minitest_go[#{args[:minitest_args]}]")
Kernel.exec("RAILS_ENV=test RACK_ENV=test #{$PROGRAM_NAME} 'knapsack_pro:queue:minitest_go[#{args[:minitest_args]}]'")
end

task :minitest_go, [:minitest_args] do |_, args|
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/queue/rspec.rake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require 'knapsack_pro'
namespace :knapsack_pro do
namespace :queue do
task :rspec, [:rspec_args] do |_, args|
Kernel.exec({ 'RAILS_ENV' => 'test', 'RACK_ENV' => 'test' }, $PROGRAM_NAME, "knapsack_pro:queue:rspec_go[#{args[:rspec_args]}]")
Kernel.exec("RAILS_ENV=test RACK_ENV=test #{$PROGRAM_NAME} 'knapsack_pro:queue:rspec_go[#{args[:rspec_args]}]'")
end

task :rspec_go, [:rspec_args] do |_, args|
Expand Down
41 changes: 11 additions & 30 deletions spec/knapsack_pro/adapters/rspec_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,36 +57,19 @@

subject { described_class.test_file_cases_for(slow_test_files) }

context 'when the rake task to detect RSpec test examples succeeded' do
it 'returns test example paths for slow test files' do
logger = instance_double(Logger)
allow(KnapsackPro).to receive(:logger).and_return(logger)
allow(logger).to receive(:info)

cmd = 'bundle exec rake knapsack_pro:rspec_test_example_detector'
env = { 'RACK_ENV' => 'test', 'RAILS_ENV' => 'test' }
expect(Kernel).to receive(:system).with(env, cmd).and_return(true)

rspec_test_example_detector = instance_double(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector)
expect(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector).to receive(:new).and_return(rspec_test_example_detector)

test_file_example_paths = double
expect(rspec_test_example_detector).to receive(:test_file_example_paths).and_return(test_file_example_paths)

expect(subject).to eq test_file_example_paths
before do
logger = instance_double(Logger)
expect(KnapsackPro).to receive(:logger).and_return(logger)
expect(logger).to receive(:info).with("Generating RSpec test examples JSON report for slow test files to prepare it to be split by test examples (by individual test cases). Thanks to that, a single slow test file can be split across parallel CI nodes. Analyzing 5 slow test files.")

expect(logger).to have_received(:info).with("Generating RSpec test examples JSON report for slow test files to prepare it to be split by test examples (by individual test cases). Thanks to that, a single slow test file can be split across parallel CI nodes. Analyzing 5 slow test files.")
end
cmd = 'RACK_ENV=test RAILS_ENV=test bundle exec rake knapsack_pro:rspec_test_example_detector'
expect(Kernel).to receive(:system).with(cmd).and_return(cmd_result)
end

context 'when KNAPSACK_PRO_RSPEC_TEST_EXAMPLE_DETECTOR_PREFIX is set with a custom environment variable' do
it 'calls Kernel.system using a single command including ENVs that is passed to a shell (does not work in a distroless environment)' do
stub_const('ENV', { 'KNAPSACK_PRO_RSPEC_TEST_EXAMPLE_DETECTOR_PREFIX' => 'CUSTOM_ENV_VAR=123 bundle exec' })

cmd = 'CUSTOM_ENV_VAR=123 bundle exec rake knapsack_pro:rspec_test_example_detector'
env = { 'RACK_ENV' => 'test', 'RAILS_ENV' => 'test' }
expect(Kernel).to receive(:system).with(env, cmd).and_return(true)
context 'when the rake task to detect RSpec test examples succeeded' do
let(:cmd_result) { true }

it 'returns test example paths for slow test files' do
rspec_test_example_detector = instance_double(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector)
expect(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector).to receive(:new).and_return(rspec_test_example_detector)

Expand All @@ -98,11 +81,9 @@
end

context 'when the rake task to detect RSpec test examples failed' do
it do
cmd = 'bundle exec rake knapsack_pro:rspec_test_example_detector'
env = { 'RACK_ENV' => 'test', 'RAILS_ENV' => 'test' }
expect(Kernel).to receive(:system).with(env, cmd).and_return(false)
let(:cmd_result) { false }

it do
expect { subject }.to raise_error(RuntimeError, 'Could not generate JSON report for RSpec. Rake task failed when running RACK_ENV=test RAILS_ENV=test bundle exec rake knapsack_pro:rspec_test_example_detector')
end
end
Expand Down
8 changes: 2 additions & 6 deletions spec/knapsack_pro/runners/spinach_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@

context 'when test files were returned by Knapsack Pro API' do
let(:test_file_paths) { ['features/a.feature', 'features/b.feature'] }
let(:test_dir) { 'fake-test-dir' }
let(:stringify_test_file_paths) { test_file_paths.join(' ') }
let(:test_dir) { 'fake-test-dir' }
let(:runner) do
instance_double(described_class,
test_dir: test_dir,
test_file_paths: test_file_paths,
stringify_test_file_paths: stringify_test_file_paths,
test_files_to_execute_exist?: true)
end
let(:child_status) { double }

it do
expect(KnapsackPro::Adapters::SpinachAdapter).to receive(:verify_bind_method_called)
Expand All @@ -37,10 +36,7 @@
expect(KnapsackPro).to receive(:tracker).and_return(tracker)
expect(tracker).to receive(:set_prerun_tests).with(test_file_paths)

expect(Kernel).to receive(:exec).with(
{ 'KNAPSACK_PRO_REGULAR_MODE_ENABLED' => 'true', 'KNAPSACK_PRO_TEST_SUITE_TOKEN' => 'spinach-token' },
'bundle exec spinach --custom-arg --features_path fake-test-dir -- features/a.feature features/b.feature'
)
expect(Kernel).to receive(:exec).with('KNAPSACK_PRO_REGULAR_MODE_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=spinach-token bundle exec spinach --custom-arg --features_path fake-test-dir -- features/a.feature features/b.feature')

subject
end
Expand Down