pax_global_header 0000666 0000000 0000000 00000000064 15043316274 0014517 g ustar 00root root 0000000 0000000 52 comment=2c97ebe614d9a389766b3e485a4ef85f4acc87b8
parallel_tests-5.4.0/ 0000775 0000000 0000000 00000000000 15043316274 0014543 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/.github/ 0000775 0000000 0000000 00000000000 15043316274 0016103 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000000470 15043316274 0021705 0 ustar 00root root 0000000 0000000 Thank you for your contribution!
## Checklist
- [ ] Feature branch is up-to-date with `master` (if not - rebase it).
- [ ] Added tests.
- [ ] Added an entry to the [Changelog](../blob/master/CHANGELOG.md) if the new
code introduces user-observable changes.
- [ ] Update Readme.md when cli options are changed
parallel_tests-5.4.0/.github/workflows/ 0000775 0000000 0000000 00000000000 15043316274 0020140 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/.github/workflows/test.yml 0000664 0000000 0000000 00000002061 15043316274 0021641 0 ustar 00root root 0000000 0000000 name: test
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # run all tests so we see which gem/ruby combinations break
matrix:
ruby: ['3.1', '3.2', '3.3', '3.4', head, jruby-head]
os: [ubuntu-latest, windows-latest]
task: [spec]
include:
- ruby: '3.1' # lowest supported version, same as gemspec and .rubocop.yml
os: ubuntu-latest
task: rubocop
steps:
- uses: actions/checkout@master
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: rake ${{ matrix.task }}
# allow ruby/jruby-head to fail since they are moving targets
# TODO: this will always show green, fix once https://github.com/actions/toolkit/issues/399 is resolved
continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
run: bundle exec rake ${{ matrix.task }}
parallel_tests-5.4.0/.gitignore 0000664 0000000 0000000 00000000051 15043316274 0016527 0 ustar 00root root 0000000 0000000 *.sh
tmp
.bundle
**/vendor/bundle
/.idea
parallel_tests-5.4.0/.rspec 0000664 0000000 0000000 00000000074 15043316274 0015661 0 ustar 00root root 0000000 0000000 --color
--order random
--exclude-pattern spec/fixtures/**/*
parallel_tests-5.4.0/.rubocop.yml 0000664 0000000 0000000 00000004375 15043316274 0017026 0 ustar 00root root 0000000 0000000 AllCops:
NewCops: enable
TargetRubyVersion: 3.1
SuggestExtensions: false
Exclude:
- '**/vendor/bundle/**/*'
- 'spec/fixtures/*/db/schema.rb'
Style/FrozenStringLiteralComment:
Enabled: false
Style/StringLiterals:
Enabled: false
Style/StringLiteralsInInterpolation:
Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: false
Bundler/OrderedGems:
Enabled: false
Metrics:
Enabled: false
Style/Documentation:
Enabled: false
Layout/EmptyLineAfterMagicComment:
Enabled: false
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
Layout/MultilineOperationIndentation:
Enabled: false
Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented
Style/NumericPredicate:
EnforcedStyle: comparison
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/FirstHashElementLineBreak:
Enabled: true # Opt-in
# Opt-in
Layout/FirstMethodArgumentLineBreak:
Enabled: true # Opt-in
Layout/FirstMethodParameterLineBreak:
Enabled: true # Opt-in
# https://github.com/rubocop-hq/rubocop/issues/5891
Style/SpecialGlobalVars:
Enabled: false
Style/GlobalStdStream:
Enabled: false
Style/WordArray:
EnforcedStyle: brackets
Style/SymbolArray:
EnforcedStyle: brackets
Style/DoubleNegation:
Enabled: false
Style/NumericLiterals:
Enabled: false
Layout/LineLength:
Enabled: false
Max: 120
Style/RedundantConstantBase:
Enabled: false
Style/RegexpLiteral:
Enabled: false
Style/Lambda:
EnforcedStyle: literal
Style/IfUnlessModifier:
Enabled: false
Style/FormatString:
Enabled: false
Naming/VariableNumber:
Enabled: false
Naming/MethodParameterName:
Enabled: false
Style/GuardClause:
Enabled: false
Lint/AssignmentInCondition:
Enabled: false
Style/Next:
Enabled: false
Naming/HeredocDelimiterNaming:
Enabled: false
Style/Semicolon:
Enabled: false
Lint/InterpolationCheck:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/TrivialAccessors:
Enabled: false
Style/ClassVars:
Enabled: false
Style/CaseEquality:
Enabled: false
Lint/EmptyClass:
Enabled: false
# ENV.fetch('FOO', nil) is the same as ENV['FOO']
Style/FetchEnvVar:
Enabled: false
# &block is pretty readable
Naming/BlockForwarding:
Enabled: false
# &block is pretty readable
Style/ArgumentsForwarding:
Enabled: false
parallel_tests-5.4.0/CHANGELOG.md 0000664 0000000 0000000 00000027076 15043316274 0016370 0 ustar 00root root 0000000 0000000 # Changelog
## Unreleased
only add here if you are working on a PR
### Breaking Changes
### Added
### Fixed
## 5.4.0 - 2025-08-01
### Added
- Rake tasks will prioritize the `PARALLEL_RAILS_ENV` value over the default `test` environment
## 5.3.1 - 2025-07-23
### Fixed
- The `--multiply-processes` option was being parsed into `options[:multiply-processes]` but was being referenced as `options[:multiply]` in the code
## 5.3.0 - 2025-05-30
### Added
- The `--exec-args` option, which allows users to run shell commands in parallel with test files as arguments
## 5.2.0 - 2025-05-08
### Added
- The `specify-groups` option supports reading from STDIN when set to `-`
## 5.1.0 - 2025-03-09
### Fixed
- Restored jruby support by restoring ruby 3.1 support
## 5.0.1 - 2025-03-05
### Fixed
- Fix Cucumber failures logger when a runner doesn't have any failed examples
## 5.0.0 - 2025-03-01
### Breaking Changes
- dropped ruby 3.0 and 3.1, added ruby 3.4
## 4.10.1 - 2025-03-01
### Fixed
- reverted determine_number_of_processes rename since that broke dependencies
## 4.10.0 - 2025-02-28
### Added
- Allow processor multiplier (flag: `-m` or `--multiply-processes`) to be set via the environment variable `PARALLEL_TEST_MULTIPLY_PROCESSES`
## 4.9.1 - 2025-02-19
### Fixed
- Fix output of Cucumber failures logger. Previously, an event handler inherited from `Cucumber::Formatter::Rerun` would improperly join failures (e.g. `feature/one.feature:1feature/two.feature:1`). Now failures are separated with a single space.
## 4.9.0 - 2025-01-09
### Fixed
- check ActiveRecord version instead of Rails
## 4.8.0 - 2025-01-03
### Added
- add --test-file-limit option for huge windows setups that breaks command length limit
## 4.7.2 - 2024-09-09
### Fixed
- Restore support for passing custom command lines as PARALLEL_TESTS_EXECUTABLE.
- dropped ruby 2.7 support
## 4.7.1 - 2024-04-25
### Added
- Restored the `--verbose-process-command` and `--verbose-rerun-command` options, removed in version 4.0.0.
See [#952](https://github.com/grosser/parallel_tests/pull/952).
`--verbose-command` continues to be supported and is equivalent to set the 2 options above.
## 4.7.0 - 2024-04-23
### Added
- Added `--failure-exit-code [INT]` flag to specify a custom exit code when tests fail. This option allows users to define a specific exit code that the test suite should return if any tests fail.
## 4.6.1 - 2024-04-03
### Fixed
- The `--allow-duplicates` flag now runs duplicate tests in different groups
## 4.6.0 - 2024-03-25
## Added
- Add `--allow-duplicates` flag to support re-running 1 spec multiple times
## 4.5.2 - 2024-02-16
### Fixed
- do not crash when a pid file was already deleted when trying to delete it
## 4.5.1 - 2024-02-16
### Fixed
Rails 5.2 and gherkin fixes
## 4.5.0 - 2024-02-06
### Added
- Support for running tasks against individual databases in a multi-database setup with Rails >= 6.1 ([#930](https://github.com/grosser/parallel_tests/pull/930))
## 4.4.0 - 2023-12-24
### Added
- Sort the output of `runtime_logger` for RSpec to show slowest tests first
- Add new `ParallelTests::RSpec::VerboseLogger` to output detailed
information about each example and it's process as it starts and finishes.
## 4.3.0 - 2023-10-08
### Added
- Support for RSpec turnip feature files.
## 4.2.2 - 2023-09-05
### Breaking Changes
- Drop support for RSpec 2.
### Added
- Document unexpected behavior where the `--only-group` flag will also set a
grouping strategy.
## 4.2.1 - 2023-05-05
### Fixed
- Fix $TEST_ENV_NUMBER replacing code to not affect all processes (#905)
- Remove duplicate raise codes. (#897)
## 4.2.0 - 2023-02-06
### Fixed
- Avoid double sending int while also not breaking debugging [#891](https://github.com/grosser/parallel_tests/pull/891)
## 4.1.0 - 2023-01-14
### Fixed
- Avoid double sending of SIGINT to subprocesses [#889](https://github.com/grosser/parallel_tests/pull/889)
## 4.0.0 - 2022-11-05
### Breaking Changes
- The `--verbose-process-command` and `--verbose-rerun-command` are combined into `--verbose-command`. See [#884](https://github.com/grosser/parallel_tests/pull/884).
- Drop ruby 2.6 support
## 3.13.0 - 2022-09-23
### Changed
- Drop support for ruby 2.5
## v3.12.1 - 2022-09-12
### Fixed
- `--quiet` no longer prints 'Using recorded test runtime'
## v3.12.0 - 2022-08-30
### Fixed
- Grouping by scenarios now works for tests that are nested under Rules.
## 3.11.0 - 2022-05-27
### Changed
- Raise a custom `RuntimeLogTooSmallError` exception when the runtime log is too
small instead of a generic `RuntimeError`.
## 3.10.1 - 2022-05-23
### Fixed
- Running rake tasks with number of processes or extra args
## 3.10.0 - 2022-05-23
### Added
- Changed Rake subtasks to always use the same Rake executable as the parent
process.
## 3.9.1 - 2022-05-23
### Fixed
- Fixed `NoMethodError` exception when running Rake task `parallel:setup`.
## 3.9.0 - 2022-05-22
### Added
- Subprocesses execute without a shell.
## 3.8.1 - 2022-03-28
### Added
- Support Ruby 2.5 / 2.6
## 3.8.0 - 2022-03-26
### Breaking Changes
- Drop support for ruby 2.5 / 2.6
### Added
- Tested on ruby 3.0 and 3.1
### Fixed
- Added Rails 7.0 to fixtures
- Fixes deprecation warning around the usage of `ActiveRecord::Base.schema_format` and deprecation in Rails 7.1
## v3.7.1 - 2021-08-14
### Breaking Changes
- None
### Added
- None
### Fixed
- All cucumber options are now pushed to the end of the command invocation
- Fixes an issue where the `--retry` flag wouldn't work correctly
## v3.7.0 - 2021-04-08
### Breaking Changes
- None
### Added
- Added `--highest-exit-status` option to return the highest exit status to allow sub-processes to send things other than 1
### Fixed
- None
## v3.6.0 - 2021-03-25
### Breaking Changes
- Drop ruby 2.4 support
### Added
- Run default test folder if no arguments are passed.
### Fixed
- None
## v3.5.1 - 2021-03-07
### Breaking Changes
- None
### Added
- None
### Fixed
- Do not use db:structure for rails 6.1
## v3.5.0 - 2021-02-24
### Breaking Changes
- None
### Added
- Add support for specifying exactly how isolated processes run tests with 'specify-groups' option.
- Refactorings for rubocop
### Fixed
- None
## v3.4.0 - 2020-12-24
### Breaking Changes
- None
### Added
- Colorize summarized RSpec results.([#787](https://github.com/grosser/parallel_tests/pull/787)).
### Fixed
- replace deprecated db:structure by db:schema (#801).
## 3.3.0 - 2020-09-16
### Added
- Added support for multiple isolated processes.
## 3.2.0 - 2020-08-27
### Breaking Changes
- RAILS_ENV cannot be specified for rake tasks (#776).
### Added
- None
### Fixed
- Rake tasks will no longer run against development environment when using a Spring-ified rake binstub (#776).
## 3.1.0 - 2020-07-23
### Added
- `--fail-fast` stops all groups if one group fails. Can be used to stop all groups if one test failed by using `fail-fast` in the test-framework too (for example rspec via `--test-options '--fail-fast'` or in `.rspec_parallel`).
## 3.0.0 - 2020-06-10
### Breaking Changes
- The `--group-by` flag with value `steps` and `features` now requires end users to add the `cuke_modeler` gem to their Gemfile (#762).
### Added
- Cucumber 4 support (#762)
### Fixed
- Fix a bundler deprecation when running specs (#761)
- remove name override logic that never worked (#758)
### Dependencies
- Drop ruby 2.3 support (#760)
- Drop ruby 2.2 support (#759)
## 2.32.0 - 2020-03-15
### Fixed
- Calculate unknown runtimes lazily when running tests grouped by runtime ([#750](https://github.com/grosser/parallel_tests/pull/750)).
## 2.31.0 - 2020-01-31
### Fixed
- File paths passed from the CLI are now cleaned (consecutive slashes and useless dots removed) ([#748](https://github.com/grosser/parallel_tests/pull/748)).
## 2.30.1 - 2020-01-14
### Added
- Add project metadata to gemspec ([#739](https://github.com/grosser/parallel_tests/pull/739)).
## Fixed
- Fix bundler deprecation warning related to `bundle show`) ([#744](https://github.com/grosser/parallel_tests/pull/744)).
- Fix numerous flakey tests ([#736](https://github.com/grosser/parallel_tests/pull/736), [#741](https://github.com/grosser/parallel_tests/pull/741)).
## 2.30.0 - 2019-12-10
### Added
- Support db:structure:dump and load structure in parallel ([#732](ht.tps://github.com/grosser/parallel_tests/pull/732)).
- Add note to the README about using the spring-commands-parallel-tests gem to automatically patch and enable Spring ([#731](https://github.com/grosser/parallel_tests/pull/731)).
### Fixed
- Refactor logic in the `parallel:prepare` task ([#737](https://github.com/grosser/parallel_tests/pull/737)).
- Update README to use :sql schema format.
- Fix loading of the `version` file when using a local git repo with Bundler ([#730](https://github.com/grosser/parallel_tests/pull/730)).
## 2.29.2 - 2019-08-06
### Fixed
- Eliminate some ruby warnings relating to ambiguous arguments, unused variables, a redefined method, and uninitialized instance variables ([#712](https://github.com/grosser/parallel_tests/pull/712)).
## 2.29.1 - 2019-06-13
### Fixed
- Fix NameError due to not requiring `shellwords` ([#707](https://github.com/grosser/parallel_tests/pull/707)).
## 2.29.0 - 2019-05-04
### Added
- `--verbose-process-command`, which prints the command that will be executed by each process before it begins ([#697](https://github.com/grosser/parallel_tests/pull/697/files)).
- `--verbose-rerun-command`, which prints the command executed by that process after a process fails ([#697](https://github.com/grosser/parallel_tests/pull/697/files)).
## 2.28.0 - 2019-02-07
### Added
- `exclude-pattern`, which excludes tests matching the passed in regex pattern ([#682](https://github.com/grosser/parallel_tests/pull/682), [#683](https://github.com/grosser/parallel_tests/pull/683)).
## 2.27.1 - 2019-01-01
### Changed
- `simulate_output_for_ci` now outputs dots (`.`) even after the first parallel thread finishes ([#673](https://github.com/grosser/parallel_tests/pull/673)).
### Fixed
- Typo in CLI options ([#672](https://github.com/grosser/parallel_tests/pull/672)).
## 2.27.0 - 2018-11-09
### Added
- Support for new Cucumber tag expressions syntax ([#668](https://github.com/grosser/parallel_tests/pull/668)).
## 2.26.2 - 2018-10-29
### Added
- `db:test:purge` is now `db:purge` so it can be used in any environment, not just the `test` environment. This change is backwards compatible. ([#665](https://github.com/grosser/parallel_tests/pull/665)).
- Tests against Rails 5.1 and 5.2 ([#663])(https://github.com/grosser/parallel_tests/pull/663)).
## 2.26.0 - 2018-10-25
### Fixed
- Update formatter to use Cucumber events API instead of deprecated API ([#664](https://github.com/grosser/parallel_tests/pull/664))
## 2.25.0 - 2018-10-24
### Fixed
- Commands and their respective outputs are now grouped together when using the `verbose` and `serialize-output` flags together ([#660](https://github.com/grosser/parallel_tests/pull/660)).
### Dependencies
- Dropped support for MiniTest 4 and Test-Unit ([#662](https://github.com/grosser/parallel_tests/pull/662)).
- Dropped support for Ruby 2.1 ([#659](https://github.com/grosser/parallel_tests/pull/659))
## 2.24.0 - 2018-10-24
### Fixed
- Improve accuracy when recording example times ([#661](https://github.com/grosser/parallel_tests/pull/661)).
### Dependencies
- Dropped support for Ruby 2.0 ([#661](https://github.com/grosser/parallel_tests/pull/661)).
## 2.23.0 - 2018-09-14
### Added
- Rake task now passes through additional arguments to the CLI ([#656](https://github.com/grosser/parallel_tests/pull/656)).
## Previous versions
No docs yet. Contributions welcome!
parallel_tests-5.4.0/Gemfile 0000664 0000000 0000000 00000000635 15043316274 0016042 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
source 'https://rubygems.org'
gemspec
gem 'bump'
gem 'test-unit'
gem 'minitest'
gem 'rspec'
gem 'cucumber'
gem 'cuke_modeler'
gem 'spinach'
gem 'racc' # need for spinach on 3.3+ https://github.com/codegram/spinach/issues/256
gem 'rake'
gem 'rubocop', '~> 1.73.1' # lock minor so we do not get accidental violations
gem 'logger' # to silence warnings in tests, not a real dependency
parallel_tests-5.4.0/Gemfile.lock 0000664 0000000 0000000 00000005520 15043316274 0016767 0 ustar 00root root 0000000 0000000 PATH
remote: .
specs:
parallel_tests (5.4.0)
parallel
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
base64 (0.3.0)
bigdecimal (3.2.2)
bigdecimal (3.2.2-java)
builder (3.3.0)
bump (0.10.0)
colorize (1.1.0)
cucumber (10.0.0)
base64 (~> 0.2)
builder (~> 3.2)
cucumber-ci-environment (> 9, < 11)
cucumber-core (> 15, < 17)
cucumber-cucumber-expressions (> 17, < 19)
cucumber-html-formatter (> 20.3, < 22)
diff-lcs (~> 1.5)
logger (~> 1.6)
mini_mime (~> 1.1)
multi_test (~> 1.1)
sys-uname (~> 1.3)
cucumber-ci-environment (10.0.1)
cucumber-core (15.1.0)
cucumber-gherkin (> 27, < 31)
cucumber-messages (> 26, < 29)
cucumber-tag-expressions (> 5, < 7)
cucumber-cucumber-expressions (18.0.1)
bigdecimal
cucumber-gherkin (30.0.4)
cucumber-messages (> 25, < 28)
cucumber-html-formatter (21.13.0)
cucumber-messages (> 19, < 28)
cucumber-messages (27.2.0)
cucumber-tag-expressions (6.1.2)
cuke_modeler (3.24.0)
cucumber-gherkin (< 33.0)
diff-lcs (1.6.2)
ffi (1.17.2)
ffi (1.17.2-java)
gherkin-ruby (0.3.2)
json (2.10.1)
json (2.10.1-java)
language_server-protocol (3.17.0.4)
lint_roller (1.1.0)
logger (1.7.0)
mini_mime (1.1.5)
minitest (5.25.4)
multi_test (1.1.0)
parallel (1.26.3)
parser (3.3.7.1)
ast (~> 2.4.1)
racc
power_assert (2.0.5)
racc (1.8.1)
racc (1.8.1-java)
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.10.0)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.3)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.2)
rubocop (1.73.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.38.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.38.1)
parser (>= 3.3.1.0)
ruby-progressbar (1.13.0)
spinach (0.12.0)
colorize
gherkin-ruby (>= 0.3.2)
sys-uname (1.3.1)
ffi (~> 1.1)
test-unit (3.6.7)
power_assert
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
PLATFORMS
java
ruby
x64-mingw32
x86_64-linux
DEPENDENCIES
bump
cucumber
cuke_modeler
logger
minitest
parallel_tests!
racc
rake
rspec
rubocop (~> 1.73.1)
spinach
test-unit
BUNDLED WITH
2.6.2
parallel_tests-5.4.0/Rakefile 0000664 0000000 0000000 00000002033 15043316274 0016206 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'bundler/setup'
require 'bump/tasks'
require 'bundler/gem_tasks'
# update all versions so bundling does not fail on CI
Bump.replace_in_default = Dir["spec/fixtures/rails*/Gemfile.lock"]
task default: [:spec, :rubocop]
task :spec do
sh "rspec spec/"
end
desc "Run rubocop"
task :rubocop do
sh "rubocop --parallel"
end
desc "bundle all gemfiles [EXTRA=]"
task :bundle_all do
extra = ENV["EXTRA"] || "install"
gemfiles = (["Gemfile"] + Dir["spec/fixtures/rails*/Gemfile"])
raise if gemfiles.size < 3
gemfiles.each do |gemfile|
Bundler.with_unbundled_env do
sh "GEMFILE=#{gemfile} bundle #{extra}"
end
end
end
desc "render the README option section"
task :readme do
output = `bundle exec ./bin/parallel_test -h`
abort "Command failed: #{output}" unless $?.success?
output.sub!(/.*Options are:/m, "") || raise
file = "README.md"
separator = ""
parts = File.read(file).split(separator)
parts[1] = output
File.write file, parts.join(separator)
end
parallel_tests-5.4.0/Readme.md 0000664 0000000 0000000 00000052502 15043316274 0016266 0 ustar 00root root 0000000 0000000 # parallel_tests
[](https://rubygems.org/gems/parallel_tests)
[](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest&branch=master)
Speedup Minitest + RSpec + Turnip + Cucumber + Spinach by running parallel on multiple CPU cores.
ParallelTests splits tests into balanced groups (by number of lines or runtime) and runs each group in a process with its own database.
Setup for Rails
===============
[RailsCasts episode #413 Fast Tests](http://railscasts.com/episodes/413-fast-tests)
### Install
`Gemfile`:
```ruby
gem 'parallel_tests', group: [:development, :test]
```
### Add to `config/database.yml`
ParallelTests uses 1 database per test-process.
| Process number | 1 | 2 | 3 |
| ENV['TEST_ENV_NUMBER'] | '' | '2' | '3' |
```yaml
test:
database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>
```
### Create additional database(s)
rake parallel:create
### (Multi-DB) Create individual database
rake parallel:create:
rake parallel:create:secondary
### Copy development schema (repeat after migrations)
rake parallel:prepare
### Run migrations in additional database(s) (repeat after migrations)
rake parallel:migrate
### (Multi-DB) Run migrations in individual database
rake parallel:migrate:
### Setup environment from scratch (create db and loads schema, useful for CI)
rake parallel:setup
### Drop all test databases
rake parallel:drop
### (Multi-DB) Drop individual test database
rake parallel:drop:
### Run!
rake parallel:test # Minitest
rake parallel:spec # RSpec
rake parallel:features # Cucumber
rake parallel:features-spinach # Spinach
rake "parallel:test[1]" --> force 1 CPU --> 86 seconds
rake parallel:test --> got 2 CPUs? --> 47 seconds
rake parallel:test --> got 4 CPUs? --> 26 seconds
...
Test by pattern with Regex (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
rake "parallel:test[^test/unit]" # every test file in test/unit folder
rake "parallel:test[user]" # run users_controller + user_helper + user tests
rake "parallel:test['user|product']" # run user and product related tests
rake "parallel:spec['spec\/(?!features)']" # run RSpec tests except the tests in spec/features
### Example output
2 processes for 210 specs, ~ 105 specs per process
... test output ...
843 examples, 0 failures, 1 pending
Took 29.925333 seconds
### Run an arbitrary task in parallel
```Bash
RAILS_ENV=test parallel_test -e "rake my:custom:task"
# or
rake "parallel:rake[my:custom:task]"
# limited parallelism
rake "parallel:rake[my:custom:task,2]"
```
Running setup or teardown once
===================
```Ruby
require "parallel_tests"
# preparation:
# affected by race-condition: first process may boot slower than the second
# the Process.ppid will be the pod of the process that started the parallel tests
# when not using TEST_ENV_NUMBER we use a unique file per process because ppid would be the users shell
done = "/tmp/parallel-setup-done-#{ENV['TEST_ENV_NUMBER'] ? Process.ppid : Process.pid}"
if ParallelTests.first_process?
do_something
File.write done, "true"
else
sleep 0.1 until File.exist?(done)
end
# cleanup:
# could also use last_process? but that is just the last process to start, not the last to finish
at_exit do
if ParallelTests.first_process?
File.unlink done
ParallelTests.wait_for_other_processes_to_finish
undo_something
end
end
```
Even test group runtimes
========================
Test groups will often run for different times, making the full test run as slow as the slowest group.
**Step 1**: Use these loggers (see below) to record test runtime
**Step 2**: The next test run will use the recorded test runtimes (use `--runtime-log ` if you picked a location different from below)
**Step 3**: Automate upload/download of test runtime from your CI system [example](https://github.com/grosser/parallel_rails_example/blob/master/.github/workflows/test.yml) (chunks need to be combined, an alternative is [amend](https://github.com/grosser/amend))
### RSpec
Rspec: Add to your `.rspec_parallel` (or `.rspec`), but can also be used via `--test-options='--format x'`:
--format progress
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
To use a custom logfile location (default: `tmp/parallel_runtime_rspec.log`), use the CLI: `parallel_test spec -t rspec --runtime-log my.log`
### Minitest
Add to your `test_helper.rb`:
```ruby
require 'parallel_tests/test/runtime_logger' if ENV['RECORD_RUNTIME']
```
results will be logged to `tmp/parallel_runtime_test.log` when `RECORD_RUNTIME` is set,
so it is not always required or overwritten.
Loggers
=======
RSpec: SummaryLogger
--------------------
Log the test output without the different processes overwriting each other.
Add the following to your `.rspec_parallel` (or `.rspec`), but can also be used via `--test-options='--format x'`:
--format progress
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
RSpec: FailuresLogger
-----------------------
Produce pasteable command-line snippets for each failed example. For example:
```bash
rspec /path/to/my_spec.rb:123 # should do something
```
Add the following to your `.rspec_parallel` (or `.rspec`), but can also be used via `--test-options='--format x'`:
--format progress
--format ParallelTests::RSpec::FailuresLogger --out tmp/failing_specs.log
(Not needed to retry failures, for that pass [--only-failures](https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures) to rspec)
RSpec: VerboseLogger
-----------------------
Prints a single line for starting and finishing each example, to see what is currently running in each process.
```
# PID, parallel process number, spec status, example description
[14403] [2] [STARTED] Foo foo
[14402] [1] [STARTED] Bar bar
[14402] [1] [PASSED] Bar bar
```
Add the following to your `.rspec_parallel` (or `.rspec`), but can also be used via `--test-options='--format x'`:
--format ParallelTests::RSpec::VerboseLogger
Cucumber: FailuresLogger
-----------------------
Log failed cucumber scenarios to the specified file. The filename can be passed to cucumber, prefixed with '@' to rerun failures.
Usage:
cucumber --format ParallelTests::Cucumber::FailuresLogger --out tmp/cucumber_failures.log
Or add the formatter to the `parallel:` profile of your `cucumber.yml`:
parallel: --format progress --format ParallelTests::Cucumber::FailuresLogger --out tmp/cucumber_failures.log
but can also be used via `--test-options='--format x'`:
Note if your `cucumber.yml` default profile uses `<%= std_opts %>` you may need to insert this as follows `parallel: <%= std_opts %> --format progress...`
To rerun failures:
cucumber @tmp/cucumber_failures.log
Setup for non-rails
===================
gem install parallel_tests
# go to your project dir
parallel_test
parallel_rspec
parallel_cucumber
parallel_spinach
- use `ENV['TEST_ENV_NUMBER']` inside your tests to select separate db/memcache/etc. (docker compose: expose it)
- Only run a subset of files / folders:
`parallel_test test/bar test/baz/foo_text.rb`
- Pass test-options and files via `--`:
`parallel_rspec -- -t acceptance -f progress -- spec/foo_spec.rb spec/acceptance`
- Pass in test options, by using the -o flag (wrap everything in quotes):
`parallel_cucumber -n 2 -o '-p foo_profile --tags @only_this_tag or @only_that_tag --format summary'`
Options are:
-n PROCESSES How many processes to use, default: available CPUs
-p, --pattern PATTERN run tests matching this regex pattern
--exclude-pattern PATTERN exclude tests matching this regex pattern
--group-by TYPE group tests by:
found - order of finding files
steps - number of cucumber/spinach steps
scenarios - individual cucumber scenarios
filesize - by size of the file
runtime - info from runtime log
default - runtime when runtime log is filled otherwise filesize
-m, --multiply-processes COUNT use given number as a multiplier of processes to run
-s, --single PATTERN Run all matching files in the same process
-i, --isolate Do not run any other tests in the group used by --single(-s)
--isolate-n PROCESSES Use 'isolate' singles with number of processes, default: 1
--highest-exit-status Exit with the highest exit status provided by test run(s)
--failure-exit-code INT Specify the exit code to use when tests fail
--specify-groups SPECS Use 'specify-groups' if you want to specify multiple specs running in multiple
processes in a specific formation. Commas indicate specs in the same process,
pipes indicate specs in a new process. If SPECS is a '-' the value for this
option is read from STDIN instead. Cannot use with --single, --isolate, or
--isolate-n. Ex.
$ parallel_tests -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
Process 1 will contain 1_spec.rb and 2_spec.rb
Process 2 will contain 3_spec.rb
Process 3 will contain all other specs
--only-group GROUP_INDEX[,GROUP_INDEX]
Only run the given group numbers.
Changes `--group-by` default to 'filesize'.
-e, --exec COMMAND execute COMMAND in parallel and with ENV['TEST_ENV_NUMBER']
--exec-args COMMAND execute COMMAND in parallel with test files as arguments, for example:
$ parallel_tests --exec-args echo
> echo spec/a_spec.rb spec/b_spec.rb
-o, --test-options 'OPTIONS' execute test commands with those options
-t, --type TYPE test(default) / rspec / cucumber / spinach
--suffix PATTERN override built in test file pattern (should match suffix):
'_spec.rb$' - matches rspec files
'_(test|spec).rb$' - matches test or spec files
--serialize-stdout Serialize stdout output, nothing will be written until everything is done
--prefix-output-with-test-env-number
Prefixes test env number to the output when not using --serialize-stdout
--combine-stderr Combine stderr into stdout, useful in conjunction with --serialize-stdout
--non-parallel execute same commands but do not in parallel, needs --exec
--no-symlinks Do not traverse symbolic links to find test files
--ignore-tags PATTERN When counting steps ignore scenarios with tags that match this pattern
--nice execute test commands with low priority.
--runtime-log PATH Location of previously recorded test runtimes
--allowed-missing COUNT Allowed percentage of missing runtimes (default = 50)
--allow-duplicates When detecting files to run, allow duplicates
--unknown-runtime SECONDS Use given number as unknown runtime (otherwise use average time)
--first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
--fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
--test-file-limit LIMIT Limit to this number of files per test run by batching
(for windows set to ~100 to stay below 8192 max command limit, might have bugs from reusing test-env-number
and summarizing partial results)
--verbose Print debug output
--verbose-command Combines options --verbose-process-command and --verbose-rerun-command
--verbose-process-command Print the command that will be executed by each process before it begins
--verbose-rerun-command After a process fails, print the command executed by that process
--quiet Print only tests output
-v, --version Show Version
-h, --help Show this.
You can run any command in parallel with `-e` / `--exec`
```bash
parallel_test -n 3 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
hello from process "2"
hello from process ""
hello from process "3"
```
and pass arguments to a command with `--exec-args`
```bash
parallel_test -n 3 --exec-args echo
spec/a_spec.rb spec/b_spec.rb
spec/c_spec.rb spec/d_spec.rb
spec/e_spec.rb
```
and run multiple commands by using `sh` and `--exec-args`
```bash
parallel_test -n 3 --exec-args "sh -c \"echo 'hello world' && rspec \$@\" --"
```
TIPS
====
### RSpec
- Add a `.rspec_parallel` to use different options, e.g. **no --drb**
- Remove `--loadby` from `.rspec`
- Instantly see failures (instead of just a red F) with [rspec-instafail](https://github.com/grosser/rspec-instafail)
- Use [rspec-retry](https://github.com/NoRedInk/rspec-retry) (not rspec-rerun) to rerun failed tests.
- [JUnit formatter configuration](https://github.com/grosser/parallel_tests/wiki#with-rspec_junit_formatter----by-jgarber)
- Use [parallel_split_test](https://github.com/grosser/parallel_split_test) to run multiple scenarios in a single spec file, concurrently. (`parallel_tests` [works at the file-level and intends to stay that way](https://github.com/grosser/parallel_tests/issues/747#issuecomment-580216980))
### Cucumber
- Add a `parallel: foo` profile to your `config/cucumber.yml` and it will be used to run parallel tests
- [ReportBuilder](https://github.com/rajatthareja/ReportBuilder) can help with combining parallel test results
- Supports Cucumber 2.0+ and is actively maintained
- Combines many JSON files into a single file
- Builds a HTML report from JSON with support for debug msgs & embedded Base64 images.
### General
- [ZSH] use quotes to use rake arguments `rake "parallel:prepare[3]"`
- [Memcached] use different namespaces
e.g. `config.cache_store = ..., namespace: "test_#{ENV['TEST_ENV_NUMBER']}"`
- Debug errors that only happen with multiple files using `--verbose` and [cleanser](https://github.com/grosser/cleanser)
- `export PARALLEL_TEST_PROCESSORS=13` to override default processor count
- `export PARALLEL_TEST_MULTIPLY_PROCESSES=.5` to override default processor multiplier
- `export PARALLEL_RAILS_ENV=environment_name` to override the default `test` environment
- Shell alias: `alias prspec='parallel_rspec -m 2 --'`
- [Spring] Add the [spring-commands-parallel-tests](https://github.com/DocSpring/spring-commands-parallel-tests) gem to your `Gemfile` to get `parallel_tests` working with Spring.
- `--first-is-1` will make the first environment be `1`, so you can test while running your full suite.
`export PARALLEL_TEST_FIRST_IS_1=true` will provide the same result
- [email_spec and/or action_mailer_cache_delivery](https://github.com/grosser/parallel_tests/wiki)
- [zeus-parallel_tests](https://github.com/sevos/zeus-parallel_tests)
- [Distributed Parallel Tests on CI systems)](https://github.com/grosser/parallel_tests/wiki/Distributed-Parallel-Tests-on-CI-systems) learn how `parallel_tests` can run on distributed servers such as Travis and GitLab-CI. Also shows you how to use parallel_tests without adding `TEST_ENV_NUMBER`-backends
- [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
- [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
- [Capistrano setup](https://github.com/grosser/parallel_tests/wiki/Remotely-with-capistrano) let your tests run on a big box instead of your laptop
- Rails vs `ArgumentError: secret_key_base`: use `config.secret_key_base = Random.hex(64)`, see [rails issue](https://github.com/rails/rails/issues/53661)
Contribute your own gotchas to the [Wiki](https://github.com/grosser/parallel_tests/wiki) or even better open a PR :)
Authors
====
inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rspec-suite)
### [Contributors](https://github.com/grosser/parallel_tests/contributors)
- [Charles Finkel](http://charlesfinkel.com/)
- [Indrek Juhkam](http://urgas.eu)
- [Jason Morrison](http://jayunit.net)
- [jinzhu](http://github.com/jinzhu)
- [Joakim Kolsjö](http://www.rubyblocks.se)
- [Kevin Scaldeferri](http://kevin.scaldeferri.com/blog/)
- [Kpumuk](http://kpumuk.info/)
- [Maksim Horbul](http://github.com/mhorbul)
- [Pivotal Labs](http://www.pivotallabs.com)
- [Rohan Deshpande](http://github.com/rdeshpande)
- [Tchandy](http://thiagopradi.net/)
- [Terence Lee](http://hone.heroku.com/)
- [Will Bryant](http://willbryant.net/)
- [Fred Wu](http://fredwu.me)
- [xxx](https://github.com/xxx)
- [Levent Ali](http://purebreeze.com/)
- [Michael Kintzer](https://github.com/rockrep)
- [nathansobo](https://github.com/nathansobo)
- [Joe Yates](http://titusd.co.uk)
- [asmega](http://www.ph-lee.com)
- [Doug Barth](https://github.com/dougbarth)
- [Geoffrey Hichborn](https://github.com/phene)
- [Trae Robrock](https://github.com/trobrock)
- [Lawrence Wang](https://github.com/levity)
- [Sean Walbran](https://github.com/seanwalbran)
- [Lawrence Wang](https://github.com/levity)
- [Potapov Sergey](https://github.com/greyblake)
- [Łukasz Tackowiak](https://github.com/lukasztackowiak)
- [Pedro Carriço](https://github.com/pedrocarrico)
- [Pablo Manrubia Díez](https://github.com/pmanrubia)
- [Slawomir Smiechura](https://github.com/ssmiech)
- [Georg Friedrich](https://github.com/georg)
- [R. Tyler Croy](https://github.com/rtyler)
- [Ulrich Berkmüller](https://github.com/ulrich-berkmueller)
- [Grzegorz Derebecki](https://github.com/madmax)
- [Florian Motlik](https://github.com/flomotlik)
- [Artem Kuzko](https://github.com/akuzko)
- [Zeke Fast](https://github.com/zekefast)
- [Joseph Shraibman](https://github.com/jshraibman-mdsol)
- [David Davis](https://github.com/daviddavis)
- [Ari Pollak](https://github.com/aripollak)
- [Aaron Jensen](https://github.com/aaronjensen)
- [Artur Roszczyk](https://github.com/sevos)
- [Caleb Tomlinson](https://github.com/calebTomlinson)
- [Jawwad Ahmad](https://github.com/jawwad)
- [Iain Beeston](https://github.com/iainbeeston)
- [Alejandro Pulver](https://github.com/alepulver)
- [Felix Clack](https://github.com/felixclack)
- [Izaak Alpert](https://github.com/karlhungus)
- [Micah Geisel](https://github.com/botandrose)
- [Exoth](https://github.com/Exoth)
- [sidfarkus](https://github.com/sidfarkus)
- [Colin Harris](https://github.com/aberant)
- [Wataru MIYAGUNI](https://github.com/gongo)
- [Brandon Turner](https://github.com/blt04)
- [Matt Hodgson](https://github.com/mhodgson)
- [bicarbon8](https://github.com/bicarbon8)
- [seichner](https://github.com/seichner)
- [Matt Southerden](https://github.com/mattsoutherden)
- [Stanislaw Wozniak](https://github.com/sponte)
- [Dmitry Polushkin](https://github.com/dmitry)
- [Samer Masry](https://github.com/smasry)
- [Volodymyr Mykhailyk](https:/github.com/volodymyr-mykhailyk)
- [Mike Mueller](https://github.com/mmueller)
- [Aaron Jensen](https://github.com/aaronjensen)
- [Ed Slocomb](https://github.com/edslocomb)
- [Cezary Baginski](https://github.com/e2)
- [Marius Ioana](https://github.com/mariusioana)
- [Lukas Oberhuber](https://github.com/lukaso)
- [Ryan Zhang](https://github.com/ryanus)
- [Rhett Sutphin](https://github.com/rsutphin)
- [Doc Ritezel](https://github.com/ohrite)
- [Alexandre Wilhelm](https://github.com/dogild)
- [Jerry](https://github.com/boblington)
- [Aleksei Gusev](https://github.com/hron)
- [Scott Olsen](https://github.com/scottolsen)
- [Andrei Botalov](https://github.com/abotalov)
- [Zachary Attas](https://github.com/snackattas)
- [David Rodríguez](https://github.com/deivid-rodriguez)
- [Justin Doody](https://github.com/justindoody)
- [Sandeep Singh](https://github.com/sandeepnagra)
- [Calaway](https://github.com/calaway)
- [alboyadjian](https://github.com/alboyadjian)
- [Nathan Broadbent](https://github.com/ndbroadbent)
- [Vikram B Kumar](https://github.com/v-kumar)
- [Joshua Pinter](https://github.com/joshuapinter)
- [Zach Dennis](https://github.com/zdennis)
- [Jon Dufresne](https://github.com/jdufresne)
- [Eric Kessler](https://github.com/enkessler)
- [Adis Osmonov](https://github.com/adis-io)
- [Josh Westbrook](https://github.com/joshwestbrook)
- [Jay Dorsey](https://github.com/jaydorsey)
- [hatsu](https://github.com/hatsu38)
- [Mark Huk](https://github.com/vimutter)
- [Johannes Vetter](https://github.com/johvet)
- [Michel Filipe](https://github.com/mfilipe)
[Michael Grosser](http://grosser.it)
michael@grosser.it
License: MIT
parallel_tests-5.4.0/bin/ 0000775 0000000 0000000 00000000000 15043316274 0015313 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/bin/parallel_cucumber 0000775 0000000 0000000 00000000423 15043316274 0020721 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
# frozen_string_literal: true
# enable local usage from cloned repo
root = File.expand_path('..', __dir__)
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
require "parallel_tests"
ParallelTests::CLI.new.run(["--type", "cucumber"] + ARGV)
parallel_tests-5.4.0/bin/parallel_rspec 0000775 0000000 0000000 00000000420 15043316274 0020225 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
# frozen_string_literal: true
# enable local usage from cloned repo
root = File.expand_path('..', __dir__)
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
require "parallel_tests"
ParallelTests::CLI.new.run(["--type", "rspec"] + ARGV)
parallel_tests-5.4.0/bin/parallel_spinach 0000775 0000000 0000000 00000000422 15043316274 0020540 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
# frozen_string_literal: true
# enable local usage from cloned repo
root = File.expand_path('..', __dir__)
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
require "parallel_tests"
ParallelTests::CLI.new.run(["--type", "spinach"] + ARGV)
parallel_tests-5.4.0/bin/parallel_test 0000775 0000000 0000000 00000000417 15043316274 0020076 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
# frozen_string_literal: true
# enable local usage from cloned repo
root = File.expand_path('..', __dir__)
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
require "parallel_tests"
ParallelTests::CLI.new.run(["--type", "test"] + ARGV)
parallel_tests-5.4.0/lib/ 0000775 0000000 0000000 00000000000 15043316274 0015311 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests.rb 0000664 0000000 0000000 00000006126 15043316274 0020661 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "parallel"
require "parallel_tests/railtie" if defined? Rails::Railtie
require "rbconfig"
module ParallelTests
WINDOWS = (RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/)
RUBY_BINARY = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
DEFAULT_MULTIPLY_PROCESSES = 1.0
autoload :CLI, "parallel_tests/cli"
autoload :VERSION, "parallel_tests/version"
autoload :Grouper, "parallel_tests/grouper"
autoload :Pids, "parallel_tests/pids"
class << self
# used by external libraries, do not rename or change api
def determine_number_of_processes(count)
Integer(
[
count,
ENV["PARALLEL_TEST_PROCESSORS"],
Parallel.processor_count
].detect { |c| !c.to_s.strip.empty? }
)
end
def determine_multiple(multiple)
Float(
[
multiple,
ENV["PARALLEL_TEST_MULTIPLY_PROCESSES"],
DEFAULT_MULTIPLY_PROCESSES
].detect { |c| !c.to_s.strip.empty? }
)
end
def with_pid_file
Tempfile.open('parallel_tests-pidfile') do |f|
ENV['PARALLEL_PID_FILE'] = f.path
# Pids object should be created before threads will start adding pids to it
# Otherwise we would have to use Mutex to prevent creation of several instances
@pids = pids
yield
ensure
ENV['PARALLEL_PID_FILE'] = nil
@pids = nil
end
end
def pids
@pids ||= Pids.new(pid_file_path)
end
def pid_file_path
ENV.fetch('PARALLEL_PID_FILE')
end
def stop_all_processes
pids.all.each { |pid| Process.kill(:INT, pid) }
rescue Errno::ESRCH, Errno::EPERM
# Process already terminated, do nothing
end
# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
def bundler_enabled?
return true if Object.const_defined?(:Bundler)
previous = nil
current = File.expand_path(Dir.pwd)
until !File.directory?(current) || current == previous
filename = File.join(current, "Gemfile")
return true if File.exist?(filename)
previous = current
current = File.expand_path("..", current)
end
false
end
def first_process?
ENV["TEST_ENV_NUMBER"].to_i <= 1
end
def last_process?
current_process_number = ENV['TEST_ENV_NUMBER']
total_processes = ENV['PARALLEL_TEST_GROUPS']
return true if current_process_number.nil? && total_processes.nil?
current_process_number = '1' if current_process_number.nil?
current_process_number == total_processes
end
def with_ruby_binary(command)
WINDOWS ? [RUBY_BINARY, '--', command] : [command]
end
def wait_for_other_processes_to_finish
return unless ENV["TEST_ENV_NUMBER"]
sleep 1 until number_of_running_processes <= 1
end
def number_of_running_processes
pids.count
end
def now
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
def delta
before = now.to_f
yield
now.to_f - before
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/ 0000775 0000000 0000000 00000000000 15043316274 0020327 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests/cli.rb 0000664 0000000 0000000 00000045317 15043316274 0021435 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'optparse'
require 'tempfile'
require 'parallel_tests'
require 'shellwords'
require 'pathname'
module ParallelTests
class CLI
def run(argv)
Signal.trap("INT") { handle_interrupt }
options = parse_options!(argv)
ENV['DISABLE_SPRING'] ||= '1'
num_processes = ParallelTests.determine_number_of_processes(options[:count])
num_processes = (num_processes * ParallelTests.determine_multiple(options[:multiply_processes])).round
options[:first_is_1] ||= first_is_1?
if options[:execute]
execute_command_in_parallel(options[:execute], num_processes, options)
else
run_tests_in_parallel(num_processes, options)
end
end
private
def handle_interrupt
@graceful_shutdown_attempted ||= false
Kernel.exit if @graceful_shutdown_attempted
# In a shell, all sub-processes also get an interrupt, so they shut themselves down.
# In a background process this does not happen and we need to do it ourselves.
# We cannot always send the interrupt since then the sub-processes would get interrupted twice when in foreground
# and that messes with interrupt handling.
#
# (can simulate detached with `(bundle exec parallel_rspec test/a_spec.rb -n 2 &)`)
# also the integration test "passes on int signal to child processes" is detached.
#
# On windows getpgid does not work so we resort to always killing which is the smaller bug.
#
# The ParallelTests::Pids `synchronize` method can't be called directly from a trap,
# using Thread workaround https://github.com/ddollar/foreman/issues/332
Thread.new do
if Gem.win_platform? || ((child_pid = ParallelTests.pids.all.first) && Process.getpgid(child_pid) != Process.pid)
ParallelTests.stop_all_processes
end
end
@graceful_shutdown_attempted = true
end
def execute_in_parallel(items, num_processes, options)
Tempfile.open 'parallel_tests-lock' do |lock|
ParallelTests.with_pid_file do
simulate_output_for_ci options[:serialize_stdout] do
Parallel.map_with_index(items, in_threads: num_processes) do |item, index|
result = yield(item, index)
reprint_output(result, lock.path) if options[:serialize_stdout]
ParallelTests.stop_all_processes if options[:fail_fast] && result[:exit_status] != 0
result
end
end
end
end
end
def run_tests_in_parallel(num_processes, options)
test_results = nil
run_tests_proc = -> do
groups = @runner.tests_in_groups(options[:files], num_processes, options)
groups.reject!(&:empty?)
if options[:only_group]
groups = options[:only_group].map { |i| groups[i - 1] }.compact
num_processes = 1
end
report_number_of_tests(groups) unless options[:quiet]
test_results = execute_in_parallel(groups, groups.size, options) do |group, index|
run_tests(group, index, num_processes, options)
end
report_results(test_results, options) unless options[:quiet]
end
if options[:quiet]
run_tests_proc.call
else
report_time_taken(&run_tests_proc)
end
if any_test_failed?(test_results)
warn final_fail_message
exit_status = if options[:failure_exit_code]
options[:failure_exit_code]
elsif options[:highest_exit_status]
test_results.map { |data| data.fetch(:exit_status) }.max
else
1
end
exit exit_status
end
end
def run_tests(group, process_number, num_processes, options)
if (limit = options[:test_file_limit])
# TODO: will have some bugs with summarizing results and last process
results = group.each_slice(limit).map do |slice|
@runner.run_tests(slice, process_number, num_processes, options)
end
result = results[0]
results[1..].each do |res|
result[:stdout] = result[:stdout].to_s + res[:stdout].to_s
result[:exit_status] = [res[:exit_status], result[:exit_status]].max
# adding all files back in, not using original cmd to show what was actually run
result[:command] |= res[:command]
end
result
else
@runner.run_tests(group, process_number, num_processes, options)
end
end
def reprint_output(result, lockfile)
lock(lockfile) do
$stdout.puts
$stdout.puts result[:stdout]
$stdout.flush
end
end
def lock(lockfile)
File.open(lockfile) do |lock|
lock.flock File::LOCK_EX
yield
ensure
# This shouldn't be necessary, but appears to be
lock.flock File::LOCK_UN
end
end
def report_results(test_results, options)
results = @runner.find_results(test_results.map { |result| result[:stdout] } * "")
puts ""
puts @runner.summarize_results(results)
report_failure_rerun_commmand(test_results, options)
end
def report_failure_rerun_commmand(test_results, options)
failing_sets = test_results.reject { |r| r[:exit_status] == 0 }
return if failing_sets.none?
if options[:verbose] || options[:verbose_rerun_command]
puts "\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\n"
failing_sets.each do |failing_set|
command = failing_set[:command]
command = @runner.command_with_seed(command, failing_set[:seed]) if failing_set[:seed]
@runner.print_command(command, failing_set[:env] || {})
end
end
end
def report_number_of_tests(groups)
name = @runner.test_file_name
num_processes = groups.size
num_tests = groups.map(&:size).sum
tests_per_process = (num_processes == 0 ? 0 : num_tests / num_processes)
puts "#{pluralize(num_processes, 'process')} for #{pluralize(num_tests, name)}, ~ #{pluralize(tests_per_process, name)} per process"
end
def pluralize(n, singular)
if n == 1
"1 #{singular}"
elsif singular.end_with?('s', 'sh', 'ch', 'x', 'z')
"#{n} #{singular}es"
else
"#{n} #{singular}s"
end
end
# exit with correct status code so rake parallel:test && echo 123 works
def any_test_failed?(test_results)
test_results.any? { |result| result[:exit_status] != 0 }
end
def parse_options!(argv)
newline_padding = 37 # poor man's way of getting a decent table like layout for -h output on 120 char width terminal
options = {}
OptionParser.new do |opts|
opts.banner = <<~BANNER
Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
[optional] Only selected files & folders:
parallel_test test/bar test/baz/xxx_text.rb
[optional] Pass test-options and files via `--`:
parallel_test -- -t acceptance -f progress -- spec/foo_spec.rb spec/acceptance
Options are:
BANNER
opts.on("-n PROCESSES", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
opts.on("-p", "--pattern PATTERN", "run tests matching this regex pattern") { |pattern| options[:pattern] = /#{pattern}/ }
opts.on("--exclude-pattern", "--exclude-pattern PATTERN", "exclude tests matching this regex pattern") { |pattern| options[:exclude_pattern] = /#{pattern}/ }
opts.on(
"--group-by TYPE",
heredoc(<<~TEXT, newline_padding)
group tests by:
found - order of finding files
steps - number of cucumber/spinach steps
scenarios - individual cucumber scenarios
filesize - by size of the file
runtime - info from runtime log
default - runtime when runtime log is filled otherwise filesize
TEXT
) { |type| options[:group_by] = type.to_sym }
opts.on("-m COUNT", "--multiply-processes COUNT", Float, "use given number as a multiplier of processes to run") do |m|
options[:multiply_processes] = m
end
opts.on("-s PATTERN", "--single PATTERN", "Run all matching files in the same process") do |pattern|
(options[:single_process] ||= []) << /#{pattern}/
end
opts.on("-i", "--isolate", "Do not run any other tests in the group used by --single(-s)") do
options[:isolate] = true
end
opts.on(
"--isolate-n PROCESSES",
Integer,
"Use 'isolate' singles with number of processes, default: 1"
) { |n| options[:isolate_count] = n }
opts.on(
"--highest-exit-status",
"Exit with the highest exit status provided by test run(s)"
) { options[:highest_exit_status] = true }
opts.on(
"--failure-exit-code INT",
Integer,
"Specify the exit code to use when tests fail"
) { |code| options[:failure_exit_code] = code }
opts.on(
"--specify-groups SPECS",
heredoc(<<~TEXT, newline_padding)
Use 'specify-groups' if you want to specify multiple specs running in multiple
processes in a specific formation. Commas indicate specs in the same process,
pipes indicate specs in a new process. If SPECS is a '-' the value for this
option is read from STDIN instead. Cannot use with --single, --isolate, or
--isolate-n. Ex.
$ parallel_tests -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
Process 1 will contain 1_spec.rb and 2_spec.rb
Process 2 will contain 3_spec.rb
Process 3 will contain all other specs
TEXT
) { |groups| options[:specify_groups] = groups }
opts.on(
"--only-group GROUP_INDEX[,GROUP_INDEX]",
Array,
heredoc(<<~TEXT, newline_padding)
Only run the given group numbers.
Changes `--group-by` default to 'filesize'.
TEXT
) { |groups| options[:only_group] = groups.map(&:to_i) }
opts.on("-e", "--exec COMMAND", "execute COMMAND in parallel and with ENV['TEST_ENV_NUMBER']") { |arg| options[:execute] = Shellwords.shellsplit(arg) }
opts.on(
"--exec-args COMMAND",
heredoc(<<~TEXT, newline_padding)
execute COMMAND in parallel with test files as arguments, for example:
$ parallel_tests --exec-args echo
> echo spec/a_spec.rb spec/b_spec.rb
TEXT
) { |arg| options[:execute_args] = Shellwords.shellsplit(arg) }
opts.on("-o", "--test-options 'OPTIONS'", "execute test commands with those options") { |arg| options[:test_options] = Shellwords.shellsplit(arg) }
opts.on("-t", "--type TYPE", "test(default) / rspec / cucumber / spinach") do |type|
@runner = load_runner(type)
rescue NameError, LoadError => e
puts "Runner for `#{type}` type has not been found! (#{e})"
abort
end
opts.on(
"--suffix PATTERN",
heredoc(<<~TEXT, newline_padding)
override built in test file pattern (should match suffix):
'_spec.rb$' - matches rspec files
'_(test|spec).rb$' - matches test or spec files
TEXT
) { |pattern| options[:suffix] = /#{pattern}/ }
opts.on("--serialize-stdout", "Serialize stdout output, nothing will be written until everything is done") { options[:serialize_stdout] = true }
opts.on("--prefix-output-with-test-env-number", "Prefixes test env number to the output when not using --serialize-stdout") { options[:prefix_output_with_test_env_number] = true }
opts.on("--combine-stderr", "Combine stderr into stdout, useful in conjunction with --serialize-stdout") { options[:combine_stderr] = true }
opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
opts.on("--no-symlinks", "Do not traverse symbolic links to find test files") { options[:symlinks] = false }
opts.on('--ignore-tags PATTERN', 'When counting steps ignore scenarios with tags that match this pattern') { |arg| options[:ignore_tag_pattern] = arg }
opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
opts.on("--runtime-log PATH", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
opts.on("--allowed-missing COUNT", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
opts.on('--allow-duplicates', 'When detecting files to run, allow duplicates') { options[:allow_duplicates] = true }
opts.on("--unknown-runtime SECONDS", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
opts.on("--fail-fast", "Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported") { options[:fail_fast] = true }
opts.on(
"--test-file-limit LIMIT",
Integer,
heredoc(<<~TEXT, newline_padding)
Limit to this number of files per test run by batching
(for windows set to ~100 to stay below 8192 max command limit, might have bugs from reusing test-env-number
and summarizing partial results)
TEXT
) { |limit| options[:test_file_limit] = limit }
opts.on("--verbose", "Print debug output") { options[:verbose] = true }
opts.on("--verbose-command", "Combines options --verbose-process-command and --verbose-rerun-command") { options.merge! verbose_process_command: true, verbose_rerun_command: true }
opts.on("--verbose-process-command", "Print the command that will be executed by each process before it begins") { options[:verbose_process_command] = true }
opts.on("--verbose-rerun-command", "After a process fails, print the command executed by that process") { options[:verbose_rerun_command] = true }
opts.on("--quiet", "Print only tests output") { options[:quiet] = true }
opts.on("-v", "--version", "Show Version") do
puts ParallelTests::VERSION
exit 0
end
opts.on("-h", "--help", "Show this.") do
puts opts
exit 0
end
end.parse!(argv)
raise "Both options are mutually exclusive: verbose & quiet" if options[:verbose] && options[:quiet]
if options[:count] == 0
options.delete(:count)
options[:non_parallel] = true
end
files, remaining = extract_file_paths(argv)
unless options[:execute]
if files.empty?
default_test_folder = @runner.default_test_folder
if File.directory?(default_test_folder)
files = [default_test_folder]
else
abort "Pass files or folders to run"
end
end
options[:files] = files.map { |file_path| Pathname.new(file_path).cleanpath.to_s }
end
append_test_options(options, remaining)
options[:group_by] ||= :filesize if options[:only_group]
if options[:group_by] == :found && options[:single_process]
raise "--group-by found and --single-process are not supported"
end
allowed = [:filesize, :runtime, :found]
if !allowed.include?(options[:group_by]) && options[:only_group]
raise "--group-by #{allowed.join(" or ")} is required for --only-group"
end
if options[:specify_groups] && options.keys.intersect?([:single_process, :isolate, :isolate_count])
raise "Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n"
end
if options[:failure_exit_code] && options[:highest_exit_status]
raise "Can't pass --failure-exit-code and --highest-exit-status"
end
options
end
def extract_file_paths(argv)
dash_index = argv.rindex("--")
file_args_at = (dash_index || -1) + 1
[argv[file_args_at..], argv[0...(dash_index || 0)]]
end
def extract_test_options(argv)
dash_index = argv.index("--") || -1
argv[dash_index + 1..]
end
def append_test_options(options, argv)
new_opts = extract_test_options(argv)
return if new_opts.empty?
options[:test_options] ||= []
options[:test_options] += new_opts
end
def load_runner(type)
require "parallel_tests/#{type}/runner"
runner_classname = type.split("_").map(&:capitalize).join.sub("Rspec", "RSpec")
klass_name = "ParallelTests::#{runner_classname}::Runner"
klass_name.split('::').inject(Object) { |x, y| x.const_get(y) }
end
def execute_command_in_parallel(command, num_processes, options)
runs = if options[:only_group]
options[:only_group].map { |g| g - 1 }
else
(0...num_processes).to_a
end
results = if options[:non_parallel]
ParallelTests.with_pid_file do
runs.map do |i|
ParallelTests::Test::Runner.execute_command(command, i, num_processes, options)
end
end
else
execute_in_parallel(runs, runs.size, options) do |i|
ParallelTests::Test::Runner.execute_command(command, i, num_processes, options)
end
end.flatten
abort if results.any? { |r| r[:exit_status] != 0 }
end
def report_time_taken(&block)
seconds = ParallelTests.delta(&block).to_i
puts "\nTook #{seconds} seconds#{detailed_duration(seconds)}"
end
def detailed_duration(seconds)
parts = [seconds / 3600, seconds % 3600 / 60, seconds % 60].drop_while(&:zero?)
return if parts.size < 2
parts = parts.map { |i| "%02d" % i }.join(':').sub(/^0/, '')
" (#{parts})"
end
def final_fail_message
fail_message = "Tests Failed"
fail_message = "\e[31m#{fail_message}\e[0m" if use_colors?
fail_message
end
def use_colors?
$stdout.tty?
end
def first_is_1?
val = ENV["PARALLEL_TEST_FIRST_IS_1"]
['1', 'true'].include?(val)
end
# CI systems often fail when there is no output for a long time, so simulate some output
def simulate_output_for_ci(simulate)
if simulate
progress_indicator = Thread.new do
interval = Float(ENV['PARALLEL_TEST_HEARTBEAT_INTERVAL'] || 60)
loop do
sleep interval
print '.'
end
end
test_results = yield
progress_indicator.exit
test_results
else
yield
end
end
def heredoc(text, newline_padding)
text.rstrip.gsub("\n", "\n#{' ' * newline_padding}")
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/cucumber/ 0000775 0000000 0000000 00000000000 15043316274 0022134 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests/cucumber/failures_logger.rb 0000664 0000000 0000000 00000001674 15043316274 0025642 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'cucumber/formatter/rerun'
require 'parallel_tests/gherkin/io'
require 'cucumber/events'
module ParallelTests
module Cucumber
class FailuresLogger < ::Cucumber::Formatter::Rerun
include ParallelTests::Gherkin::Io
def initialize(config)
super
@io = prepare_io(config.out_stream)
# Remove handler inherited from Cucumber::Formatter::Rerun that does not
# properly join file failures
handlers = config.event_bus.instance_variable_get(:@handlers)
handlers[::Cucumber::Events::TestRunFinished.to_s].pop
# Add our own handler
config.on_event :test_run_finished do
next if @failures.empty?
lock_output do
@failures.each do |file, lines|
lines.each do |line|
@io.print "#{file}:#{line} "
end
end
end
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/cucumber/features_with_steps.rb 0000664 0000000 0000000 00000002347 15043316274 0026556 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
begin
gem "cuke_modeler", "~> 3.0"
require 'cuke_modeler'
rescue LoadError
raise 'Grouping by number of cucumber steps requires the `cuke_modeler` modeler gem with requirement `~> 3.0`. Add `gem "cuke_modeler", "~> 3.0"` to your `Gemfile`, run `bundle install` and try again.'
end
module ParallelTests
module Cucumber
class FeaturesWithSteps
class << self
def all(tests, options)
ignore_tag_pattern = options[:ignore_tag_pattern].nil? ? nil : Regexp.compile(options[:ignore_tag_pattern])
# format of hash will be FILENAME => NUM_STEPS
steps_per_file = tests.each_with_object({}) do |file, steps|
feature = ::CukeModeler::FeatureFile.new(file).feature
# skip feature if it matches tag regex
next if feature.tags.grep(ignore_tag_pattern).any?
# count the number of steps in the file
# will only include a feature if the regex does not match
all_steps = feature.scenarios.map { |a| a.steps.count if a.tags.grep(ignore_tag_pattern).empty? }.compact
steps[file] = all_steps.sum
end
steps_per_file.sort_by { |_, value| -value }
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/cucumber/runner.rb 0000664 0000000 0000000 00000002227 15043316274 0023775 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "parallel_tests/gherkin/runner"
module ParallelTests
module Cucumber
class Runner < ParallelTests::Gherkin::Runner
SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios:$/
SCENARIO_REGEX = %r{^cucumber features/.+:\d+}
class << self
def name
'cucumber'
end
def default_test_folder
'features'
end
def line_is_result?(line)
super || line =~ SCENARIO_REGEX || line =~ SCENARIOS_RESULTS_BOUNDARY_REGEX
end
def summarize_results(results)
output = []
scenario_groups = results.slice_before(SCENARIOS_RESULTS_BOUNDARY_REGEX).group_by(&:first)
scenario_groups.each do |header, group|
scenarios = group.flatten.grep(SCENARIO_REGEX)
output << ([header] + scenarios).join("\n") if scenarios.any?
end
output << super
output.join("\n\n")
end
def command_with_seed(cmd, seed)
clean = remove_command_arguments(cmd, '--order')
[*clean, '--order', "random:#{seed}"]
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/cucumber/scenario_line_logger.rb 0000664 0000000 0000000 00000003303 15043316274 0026631 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ParallelTests
module Cucumber
module Formatters
class ScenarioLineLogger
attr_reader :scenarios
def initialize(tag_expression = nil)
@scenarios = []
@tag_expression = tag_expression
end
def visit_feature_element(uri, feature_element, feature_tags, line_numbers: [])
scenario_tags = feature_element.tags.map(&:name)
scenario_tags = feature_tags + scenario_tags
if feature_element.is_a?(CukeModeler::Scenario) # :Scenario
test_line = feature_element.source_line
# We don't accept the feature_element if the current tags are not valid
return unless matches_tags?(scenario_tags)
# or if it is not at the correct location
return if line_numbers.any? && !line_numbers.include?(test_line)
@scenarios << [uri, feature_element.source_line].join(":")
else # :ScenarioOutline
feature_element.examples.each do |example|
example_tags = example.tags.map(&:name)
example_tags = scenario_tags + example_tags
next unless matches_tags?(example_tags)
example.rows[1..].each do |row|
test_line = row.source_line
next if line_numbers.any? && !line_numbers.include?(test_line)
@scenarios << [uri, test_line].join(':')
end
end
end
end
def method_missing(*); end # # rubocop:disable Style/MissingRespondToMissing
private
def matches_tags?(tags)
@tag_expression.nil? || @tag_expression.evaluate(tags)
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/cucumber/scenarios.rb 0000664 0000000 0000000 00000005610 15043316274 0024451 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'cucumber/tag_expressions/parser'
require 'cucumber/runtime'
require 'cucumber'
require 'parallel_tests/cucumber/scenario_line_logger'
require 'parallel_tests/gherkin/listener'
begin
gem "cuke_modeler", "~> 3.0"
require 'cuke_modeler'
rescue LoadError
raise 'Grouping by individual cucumber scenarios requires the `cuke_modeler` modeler gem with requirement `~> 3.0`. Add `gem "cuke_modeler", "~> 3.0"` to your `Gemfile`, run `bundle install` and try again.'
end
module ParallelTests
module Cucumber
class Scenarios
class << self
def all(files, options = {})
# Parse tag expression from given test options and ignore tag pattern. Refer here to understand how new tag expression syntax works - https://github.com/cucumber/cucumber/tree/master/tag-expressions
tags = []
words = options[:test_options] || []
words.each_with_index { |w, i| tags << words[i + 1] if ["-t", "--tags"].include?(w) }
if ignore = options[:ignore_tag_pattern]
tags << "not (#{ignore})"
end
tags_exp = tags.compact.join(" and ")
split_into_scenarios files, tags_exp
end
private
def split_into_scenarios(files, tags = '')
# Create the tag expression instance from cucumber tag expressions parser, this is needed to know if the scenario matches with the tags invoked by the request
# Create the ScenarioLineLogger which will filter the scenario we want
args = []
args << ::Cucumber::TagExpressions::Parser.new.parse(tags) unless tags.empty?
scenario_line_logger = ParallelTests::Cucumber::Formatters::ScenarioLineLogger.new(*args)
# here we loop on the files map, each file will contain one or more scenario
files.each do |path|
# Gather up any line numbers attached to the file path
path, *test_lines = path.split(/:(?=\d+)/)
test_lines.map!(&:to_i)
# We create a Gherkin document, this will be used to decode the details of each scenario
document = ::CukeModeler::FeatureFile.new(path)
feature = document.feature
# We make an attempt to parse the gherkin document, this could be failed if the document is not well formatted
feature_tags = feature.tags.map(&:name)
# We loop on each children of the feature
test_models = feature.tests
test_models += feature.rules.flat_map(&:tests) if feature.respond_to?(:rules) # cuke_modeler >= 3.2 supports rules
test_models.each do |test|
# It's a scenario, we add it to the scenario_line_logger
scenario_line_logger.visit_feature_element(document.path, test, feature_tags, line_numbers: test_lines)
end
end
scenario_line_logger.scenarios
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/gherkin/ 0000775 0000000 0000000 00000000000 15043316274 0021756 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests/gherkin/io.rb 0000664 0000000 0000000 00000001473 15043316274 0022717 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests'
module ParallelTests
module Gherkin
module Io
def prepare_io(path_or_io)
if path_or_io.respond_to?(:write)
path_or_io
else # its a path
File.open(path_or_io, 'w').close # clean out the file
file = File.open(path_or_io, 'a')
at_exit do
unless file.closed?
file.flush
file.close
end
end
file
end
end
# do not let multiple processes get in each others way
def lock_output
if @io.is_a?(File)
begin
@io.flock File::LOCK_EX
yield
ensure
@io.flock File::LOCK_UN
end
else
yield
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/gherkin/listener.rb 0000664 0000000 0000000 00000004036 15043316274 0024133 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ParallelTests
module Gherkin
class Listener
attr_reader :collect
attr_writer :ignore_tag_pattern
def initialize
@steps = []
@uris = []
@collect = {}
@feature, @ignore_tag_pattern = nil
reset_counters!
end
def feature(feature)
@feature = feature
end
def background(*)
@background = 1
end
def scenario(scenario)
@outline = @background = 0
return if should_ignore(scenario)
@scenarios += 1
end
def scenario_outline(outline)
return if should_ignore(outline)
@outline = 1
end
def step(*)
return if @ignoring
if @background == 1
@background_steps += 1
elsif @outline > 0
@outline_steps += 1
else
@collect[@uri] += 1
end
end
def uri(path)
@uri = path
@collect[@uri] = 0
end
#
# @param [Gherkin::Formatter::Model::Examples] examples
#
def examples(examples)
@collect[@uri] += (@outline_steps * examples.rows.size) unless examples.rows.empty?
end
def eof(*)
@collect[@uri] += (@background_steps * @scenarios)
reset_counters!
end
def reset_counters!
@outline = @outline_steps = @background = @background_steps = @scenarios = 0
@ignoring = nil
end
# ignore lots of other possible callbacks ...
def method_missing(*); end # rubocop:disable Style/MissingRespondToMissing
private
# Return a combination of tags declared on this scenario/outline and the feature it belongs to
def all_tags(scenario)
(scenario.tags || []) + ((@feature && @feature.tags) || [])
end
# Set @ignoring if we should ignore this scenario/outline based on its tags
def should_ignore(scenario)
@ignoring = @ignore_tag_pattern && all_tags(scenario).find { |tag| @ignore_tag_pattern === tag.name }
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/gherkin/runner.rb 0000664 0000000 0000000 00000007165 15043316274 0023625 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "parallel_tests/test/runner"
module ParallelTests
module Gherkin
class Runner < ParallelTests::Test::Runner
class << self
def run_tests(test_files, process_number, num_processes, options)
combined_scenarios = test_files
if options[:group_by] == :scenarios
grouped = test_files.map { |t| t.split(':') }.group_by(&:first)
combined_scenarios = grouped.map do |file, files_and_lines|
"#{file}:#{files_and_lines.map(&:last).join(':')}"
end
end
options[:env] ||= {}
options[:env] = options[:env].merge({ 'AUTOTEST' => '1' }) if $stdout.tty?
execute_command(build_command(combined_scenarios, options), process_number, num_processes, options)
end
def test_file_name
@test_file_name || 'feature'
end
def default_test_folder
'features'
end
def test_suffix
/\.feature$/
end
def line_is_result?(line)
line =~ /^\d+ (steps?|scenarios?)/
end
def build_test_command(file_list, options)
[
*executable,
*(runtime_logging if File.directory?(File.dirname(runtime_log))),
*file_list,
*cucumber_opts(options[:test_options])
]
end
# cucumber has 2 result lines per test run, that cannot be added
# 1 scenario (1 failed)
# 1 step (1 failed)
def summarize_results(results)
sort_order = ['scenario', 'step', 'failed', 'flaky', 'undefined', 'skipped', 'pending', 'passed']
['scenario', 'step'].map do |group|
group_results = results.grep(/^\d+ #{group}/)
next if group_results.empty?
sums = sum_up_results(group_results)
sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
sums.map! do |word, number|
plural = "s" if (word == group) && (number != 1)
"#{number} #{word}#{plural}"
end
"#{sums[0]} (#{sums[1..].join(", ")})"
end.compact.join("\n")
end
def cucumber_opts(given)
if given&.include?('--profile') || given&.include?('-p')
given
else
[*given, *profile_from_config]
end
end
def profile_from_config
# copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
config = Dir.glob("{,.config/,config/}#{name}{.yml,.yaml}").first
['--profile', 'parallel'] if config && File.read(config) =~ /^parallel:/
end
def tests_in_groups(tests, num_groups, options = {})
@test_file_name = "scenario" if options[:group_by] == :scenarios
method = "by_#{options[:group_by]}"
if Grouper.respond_to?(method)
Grouper.send(method, find_tests(tests, options), num_groups, options)
else
super
end
end
def runtime_logging
['--format', 'ParallelTests::Gherkin::RuntimeLogger', '--out', runtime_log]
end
def runtime_log
"tmp/parallel_runtime_#{name}.log"
end
def determine_executable
if File.exist?("bin/#{name}")
ParallelTests.with_ruby_binary("bin/#{name}")
elsif ParallelTests.bundler_enabled?
["bundle", "exec", name]
elsif File.file?("script/#{name}")
ParallelTests.with_ruby_binary("script/#{name}")
else
[name.to_s]
end
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/gherkin/runtime_logger.rb 0000664 0000000 0000000 00000001335 15043316274 0025327 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests/gherkin/io'
module ParallelTests
module Gherkin
class RuntimeLogger
include Io
def initialize(config)
@io = prepare_io(config.out_stream)
@example_times = Hash.new(0)
config.on_event :test_case_started do |_|
@start_at = ParallelTests.now.to_f
end
config.on_event :test_case_finished do |event|
@example_times[event.test_case.location.file] += ParallelTests.now.to_f - @start_at
end
config.on_event :test_run_finished do |_|
lock_output do
@io.puts(@example_times.map { |file, time| "#{file}:#{time}" })
end
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/grouper.rb 0000664 0000000 0000000 00000012576 15043316274 0022352 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ParallelTests
class Grouper
class << self
def by_steps(tests, num_groups, options)
features_with_steps = group_by_features_with_steps(tests, options)
in_even_groups_by_size(features_with_steps, num_groups)
end
def by_scenarios(tests, num_groups, options = {})
scenarios = group_by_scenarios(tests, options)
in_even_groups_by_size(scenarios, num_groups)
end
def in_even_groups_by_size(items, num_groups, options = {})
groups = Array.new(num_groups) { { items: [], size: 0 } }
return specify_groups(items, num_groups, options, groups) if options[:specify_groups]
# add all files that should run in a single process to one group
single_process_patterns = options[:single_process] || []
single_items, items = items.partition do |item, _size|
single_process_patterns.any? { |pattern| item =~ pattern }
end
isolate_count = isolate_count(options)
if isolate_count >= num_groups
raise 'Number of isolated processes must be >= total number of processes'
end
if isolate_count >= 1
# add all files that should run in a multiple isolated processes to their own groups
group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
# group the non-isolated by size
group_features_by_size(items_to_group(items), groups[isolate_count..])
else
# add all files that should run in a single non-isolated process to first group
single_items.each { |item, size| add_to_group(groups.first, item, size) }
# group all by size
group_features_by_size(items_to_group(items), groups)
end
groups.map! { |g| g[:items].sort }
end
private
def specified_groups(options)
groups = options[:specify_groups]
return groups if groups != '-'
$stdin.read.chomp
end
def specify_groups(items, num_groups, options, groups)
specify_test_process_groups = specified_groups(options).split('|')
if specify_test_process_groups.count > num_groups
raise 'Number of processes separated by pipe must be less than or equal to the total number of processes'
end
all_specified_tests = specify_test_process_groups.map { |group| group.split(',') }.flatten
specified_items_found, items = items.partition { |item, _size| all_specified_tests.include?(item) }
specified_specs_not_found = all_specified_tests - specified_items_found.map(&:first)
if specified_specs_not_found.any?
raise "Could not find #{specified_specs_not_found} from --specify-groups in the selected files & folders"
end
if specify_test_process_groups.count == num_groups && items.flatten.any?
raise(
<<~ERROR
The number of groups in --specify-groups matches the number of groups from -n but there were other specs
found in the selected files & folders not specified in --specify-groups. Make sure -n is larger than the
number of processes in --specify-groups if there are other specs that need to be run. The specs that aren't run:
#{items.map(&:first)}
ERROR
)
end
# First order the specify_groups into the main groups array
specify_test_process_groups.each_with_index do |specify_test_process, i|
groups[i] = specify_test_process.split(',')
end
# Return early when processed specify_groups tests exactly match the items passed in
return groups if specify_test_process_groups.count == num_groups
# Now sort the rest of the items into the main groups array
specified_range = specify_test_process_groups.count..-1
remaining_groups = groups[specified_range]
group_features_by_size(items_to_group(items), remaining_groups)
# Don't sort all the groups, only sort the ones not specified in specify_groups
sorted_groups = remaining_groups.map { |g| g[:items].sort }
groups[specified_range] = sorted_groups
groups
end
def isolate_count(options)
if options[:isolate_count] && options[:isolate_count] > 1
options[:isolate_count]
elsif options[:isolate]
1
else
0
end
end
def largest_first(files)
files.sort_by { |_item, size| size }.reverse
end
def smallest_group(groups)
groups.min_by { |g| g[:size] }
end
def add_to_group(group, item, size)
group[:items] << item
group[:size] += size
end
def group_by_features_with_steps(tests, options)
require 'parallel_tests/cucumber/features_with_steps'
ParallelTests::Cucumber::FeaturesWithSteps.all(tests, options)
end
def group_by_scenarios(tests, options = {})
require 'parallel_tests/cucumber/scenarios'
ParallelTests::Cucumber::Scenarios.all(tests, options)
end
def group_features_by_size(items, groups_to_fill)
items.each do |item, size|
size ||= 1
smallest = smallest_group(groups_to_fill)
add_to_group(smallest, item, size)
end
end
def items_to_group(items)
items.first && items.first.size == 2 ? largest_first(items) : items
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/pids.rb 0000664 0000000 0000000 00000001505 15043316274 0021614 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'json'
module ParallelTests
class Pids
attr_reader :file_path, :mutex
def initialize(file_path)
@file_path = file_path
@mutex = Mutex.new
end
def add(pid)
pids << pid.to_i
save
end
def delete(pid)
pids.delete(pid.to_i)
save
end
def count
read
pids.count
end
def all
read
pids
end
private
def pids
@pids ||= []
end
def clear
@pids = []
save
end
def read
sync do
contents = File.read(file_path)
return if contents.empty?
@pids = JSON.parse(contents)
end
end
def save
sync { File.write(file_path, pids.to_json) }
end
def sync(&block)
mutex.synchronize(&block)
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/railtie.rb 0000664 0000000 0000000 00000000271 15043316274 0022305 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
# rake tasks for Rails 3+
module ParallelTests
class Railtie < ::Rails::Railtie
rake_tasks do
require "parallel_tests/tasks"
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/rspec/ 0000775 0000000 0000000 00000000000 15043316274 0021443 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests/rspec/failures_logger.rb 0000664 0000000 0000000 00000001044 15043316274 0025140 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests/rspec/logger_base'
require 'parallel_tests/rspec/runner'
class ParallelTests::RSpec::FailuresLogger < ParallelTests::RSpec::LoggerBase
RSpec::Core::Formatters.register(self, :dump_summary)
def dump_summary(*args)
lock_output do
notification = args.first
unless notification.failed_examples.empty?
colorizer = ::RSpec::Core::Formatters::ConsoleCodes
output.puts notification.colorized_rerun_commands(colorizer)
end
end
@output.flush
end
end
parallel_tests-5.4.0/lib/parallel_tests/rspec/logger_base.rb 0000664 0000000 0000000 00000001720 15043316274 0024241 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ParallelTests
module RSpec
end
end
require 'rspec/core/formatters/base_text_formatter'
class ParallelTests::RSpec::LoggerBase < RSpec::Core::Formatters::BaseTextFormatter
def initialize(*args)
super
@output ||= args[0]
case @output
when String # a path ?
FileUtils.mkdir_p(File.dirname(@output))
File.open(@output, 'w') {} # overwrite previous results
@output = File.open(@output, 'a')
when File # close and restart in append mode
@output.close
@output = File.open(@output.path, 'a')
end
end
# stolen from Rspec
def close(*)
@output.close if (IO === @output) & (@output != $stdout)
end
protected
# do not let multiple processes get in each others way
def lock_output
if @output.is_a?(File)
begin
@output.flock File::LOCK_EX
yield
ensure
@output.flock File::LOCK_UN
end
else
yield
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/rspec/runner.rb 0000664 0000000 0000000 00000004526 15043316274 0023310 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "parallel_tests/test/runner"
module ParallelTests
module RSpec
class Runner < ParallelTests::Test::Runner
class << self
def run_tests(test_files, process_number, num_processes, options)
execute_command(build_command(test_files, options), process_number, num_processes, options)
end
def determine_executable
if File.exist?("bin/rspec")
ParallelTests.with_ruby_binary("bin/rspec")
elsif ParallelTests.bundler_enabled?
["bundle", "exec", "rspec"]
else
["rspec"]
end
end
def runtime_log
"tmp/parallel_runtime_rspec.log"
end
def default_test_folder
"spec"
end
def test_file_name
"spec"
end
# used to find all _spec.rb files
# supports also feature files used by rspec turnip extension
def test_suffix
/(_spec\.rb|\.feature)$/
end
def line_is_result?(line)
line =~ /\d+ examples?, \d+ failures?/
end
def build_test_command(file_list, options)
[*executable, *options[:test_options], *color, *spec_opts, *file_list]
end
# remove old seed and add new seed
# --seed 1234
# --order rand
# --order rand:1234
# --order random:1234
def command_with_seed(cmd, seed)
clean = remove_command_arguments(cmd, '--seed', '--order')
[*clean, '--seed', seed]
end
# Summarize results from threads and colorize results based on failure and pending counts.
#
def summarize_results(results)
text = super
return text unless $stdout.tty?
sums = sum_up_results(results)
color =
if sums['failure'] > 0
31 # red
elsif sums['pending'] > 0
33 # yellow
else
32 # green
end
"\e[#{color}m#{text}\e[0m"
end
private
def color
['--color', '--tty'] if $stdout.tty?
end
def spec_opts
options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect { |f| File.file?(f) }
["-O", options_file] if options_file
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/rspec/runtime_logger.rb 0000664 0000000 0000000 00000002457 15043316274 0025022 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests'
require 'parallel_tests/rspec/logger_base'
class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
def initialize(*args)
super
@example_times = Hash.new(0)
@group_nesting = 0
end
RSpec::Core::Formatters.register(self, :example_group_started, :example_group_finished, :start_dump)
def example_group_started(example_group)
@time = ParallelTests.now if @group_nesting == 0
@group_nesting += 1
super
end
def example_group_finished(notification)
@group_nesting -= 1
if @group_nesting == 0
@example_times[notification.group.file_path] += ParallelTests.now - @time
end
super if defined?(super)
end
def seed(*); end
def dump_summary(*); end
def dump_failures(*); end
def dump_failure(*); end
def dump_pending(*); end
def start_dump(*)
return unless ENV['TEST_ENV_NUMBER'] # only record when running in parallel
lock_output do
# Order the output from slowest to fastest
@example_times = @example_times.sort_by(&:last).reverse
@example_times.each do |file, time|
relative_path = file.sub(%r{^#{Regexp.escape Dir.pwd}/}, '').sub(%r{^\./}, "")
@output.puts "#{relative_path}:#{[time, 0].max}"
end
end
@output.flush
end
end
parallel_tests-5.4.0/lib/parallel_tests/rspec/summary_logger.rb 0000664 0000000 0000000 00000000446 15043316274 0025030 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests/rspec/failures_logger'
class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
RSpec::Core::Formatters.register(self, :dump_failures)
def dump_failures(*args)
lock_output { super }
@output.flush
end
end
parallel_tests-5.4.0/lib/parallel_tests/rspec/verbose_logger.rb 0000664 0000000 0000000 00000002672 15043316274 0025003 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'rspec/core/formatters/base_text_formatter'
require 'parallel_tests/rspec/runner'
class ParallelTests::RSpec::VerboseLogger < RSpec::Core::Formatters::BaseTextFormatter
RSpec::Core::Formatters.register(
self,
:example_group_started,
:example_group_finished,
:example_started,
:example_passed,
:example_pending,
:example_failed
)
def initialize(output)
super
@line = []
end
def example_group_started(notification)
@line.push(notification.group.description)
end
def example_group_finished(_notification)
@line.pop
end
def example_started(notification)
@line.push(notification.example.description)
output_formatted_line('STARTED', :yellow)
end
def example_passed(_passed)
output_formatted_line('PASSED', :success)
@line.pop
end
def example_pending(_pending)
output_formatted_line('PENDING', :pending)
@line.pop
end
def example_failed(_failure)
output_formatted_line('FAILED', :failure)
@line.pop
end
private
def output_formatted_line(status, console_code)
prefix = ["[#{Process.pid}]"]
if ENV.include?('TEST_ENV_NUMBER')
test_env_number = ENV['TEST_ENV_NUMBER'] == '' ? 1 : Integer(ENV['TEST_ENV_NUMBER'])
prefix << "[#{test_env_number}]"
end
prefix << RSpec::Core::Formatters::ConsoleCodes.wrap("[#{status}]", console_code)
output.puts [*prefix, *@line].join(' ')
end
end
parallel_tests-5.4.0/lib/parallel_tests/spinach/ 0000775 0000000 0000000 00000000000 15043316274 0021754 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests/spinach/runner.rb 0000664 0000000 0000000 00000000626 15043316274 0023616 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "parallel_tests/gherkin/runner"
module ParallelTests
module Spinach
class Runner < ParallelTests::Gherkin::Runner
class << self
def name
'spinach'
end
def default_test_folder
'features'
end
def runtime_logging
# Not Yet Supported
[]
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/tasks.rb 0000664 0000000 0000000 00000025256 15043316274 0022013 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'rake'
require 'shellwords'
module ParallelTests
module Tasks
class << self
def rails_env
ENV['PARALLEL_RAILS_ENV'] || 'test'
end
def load_lib
$LOAD_PATH << File.expand_path('..', __dir__)
require "parallel_tests"
end
def purge_before_load
if ActiveRecord.version > Gem::Version.new('4.2.0')
Rake::Task.task_defined?('db:purge') ? 'db:purge' : 'app:db:purge'
end
end
def run_in_parallel(cmd, options = {})
load_lib
# Using the relative path to find the binary allow to run a specific version of it
executable = File.expand_path('../../bin/parallel_test', __dir__)
command = ParallelTests.with_ruby_binary(executable)
command += ['--exec', Shellwords.join(cmd)]
command += ['-n', options[:count]] unless options[:count].to_s.empty?
command << '--non-parallel' if options[:non_parallel]
abort unless system(*command)
end
# this is a crazy-complex solution for a very simple problem:
# removing certain lines from the output without changing the exit-status
# normally I'd not do this, but it has been lots of fun and a great learning experience :)
#
# - sed does not support | without -r
# - grep changes 0 exitstatus to 1 if nothing matches
# - sed changes 1 exitstatus to 0
# - pipefail makes pipe fail with exitstatus of first failed command
# - pipefail is not supported in (zsh)
# - defining a new rake task like silence_schema would force users to load parallel_tests in test env
# - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
def suppress_output(command, ignore_regex)
activate_pipefail = "set -o pipefail"
remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
# remove nil values (ex: #purge_before_load returns nil)
command.compact!
if system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null")
shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
['/bin/bash', '-c', shell_command]
else
command
end
end
def suppress_schema_load_output(command)
ParallelTests::Tasks.suppress_output(command, "^ ->\\|^-- ")
end
def check_for_pending_migrations
["db:abort_if_pending_migrations", "app:db:abort_if_pending_migrations"].each do |abort_migrations|
if Rake::Task.task_defined?(abort_migrations)
Rake::Task[abort_migrations].invoke
break
end
end
end
# parallel:spec[:count, :pattern, :options, :pass_through]
def parse_args(args)
# order as given by user
args = [args[:count], args[:pattern], args[:options], args[:pass_through]]
# count given or empty ?
# parallel:spec[2,models,options]
# parallel:spec[,models,options]
count = args.shift if args.first.to_s =~ /^\d*$/
num_processes = (count.to_s.empty? ? nil : Integer(count))
pattern = args.shift
options = args.shift
pass_through = args.shift
[num_processes, pattern, options, pass_through]
end
def schema_format_based_on_rails_version
if active_record_7_or_greater?
ActiveRecord.schema_format
else
ActiveRecord::Base.schema_format
end
end
def schema_type_based_on_rails_version
if active_record_61_or_greater? || schema_format_based_on_rails_version == :ruby
"schema"
else
"structure"
end
end
def build_run_command(type, args)
count, pattern, options, pass_through = ParallelTests::Tasks.parse_args(args)
test_framework = {
'spec' => 'rspec',
'test' => 'test',
'features' => 'cucumber',
'features-spinach' => 'spinach'
}.fetch(type)
type = 'features' if test_framework == 'spinach'
# Using the relative path to find the binary allow to run a specific version of it
executable = File.expand_path('../../bin/parallel_test', __dir__)
executable = ParallelTests.with_ruby_binary(executable)
command = [*executable, type, '--type', test_framework]
command += ['-n', count.to_s] if count
command += ['--pattern', pattern] if pattern
command += ['--test-options', options] if options
command += Shellwords.shellsplit pass_through if pass_through
command
end
def configured_databases
return [] unless defined?(ActiveRecord) && active_record_61_or_greater?
@@configured_databases ||= ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
end
def for_each_database(&block)
# Use nil to represent all databases
block&.call(nil)
# skip if not rails or old rails version
return if !defined?(ActiveRecord::Tasks::DatabaseTasks) || !ActiveRecord::Tasks::DatabaseTasks.respond_to?(:for_each)
ActiveRecord::Tasks::DatabaseTasks.for_each(configured_databases) do |name|
block&.call(name)
end
end
private
def active_record_7_or_greater?
ActiveRecord.version >= Gem::Version.new('7.0')
end
def active_record_61_or_greater?
ActiveRecord.version >= Gem::Version.new('6.1.0')
end
end
end
end
namespace :parallel do
desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
task :setup, :count do |_, args|
command = [$0, "db:setup", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
end
ParallelTests::Tasks.for_each_database do |name|
task_name = 'create'
task_name += ":#{name}" if name
desc "Create test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
task task_name.to_sym, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[$0, "db:#{task_name}", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
args
)
end
end
ParallelTests::Tasks.for_each_database do |name|
task_name = 'drop'
task_name += ":#{name}" if name
desc "Drop test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
task task_name.to_sym, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[
$0,
"db:#{task_name}",
"RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
],
args
)
end
end
desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
task(:prepare, [:count]) do |_, args|
ParallelTests::Tasks.check_for_pending_migrations
if defined?(ActiveRecord) && [:ruby, :sql].include?(ParallelTests::Tasks.schema_format_based_on_rails_version)
# fast: dump once, load in parallel
type = ParallelTests::Tasks.schema_type_based_on_rails_version
Rake::Task["db:#{type}:dump"].invoke
# remove database connection to prevent "database is being accessed by other users"
ActiveRecord::Base.remove_connection if ActiveRecord::Base.configurations.any?
Rake::Task["parallel:load_#{type}"].invoke(args[:count])
else
# slow: dump and load in in serial
args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
ParallelTests::Tasks.run_in_parallel([$0, task_name], args)
next
end
end
# when dumping/resetting takes too long
ParallelTests::Tasks.for_each_database do |name|
task_name = 'migrate'
task_name += ":#{name}" if name
desc "Update test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
task task_name.to_sym, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[$0, "db:#{task_name}", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
args
)
end
end
desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
task :rollback, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[$0, "db:rollback", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
args
)
end
# just load the schema (good for integration server <-> no development db)
ParallelTests::Tasks.for_each_database do |name|
rails_task = 'db:schema:load'
rails_task += ":#{name}" if name
task_name = 'load_schema'
task_name += ":#{name}" if name
desc "Load dumped schema for test#{" #{name}" if name} database via #{rails_task} --> parallel:#{task_name}[num_cpus]"
task task_name.to_sym, :count do |_, args|
command = [
$0,
ParallelTests::Tasks.purge_before_load,
rails_task,
"RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
]
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
end
end
# load the structure from the structure.sql file
# (faster for rails < 6.1, deprecated after and only configured by `ActiveRecord::Base.schema_format`)
desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
task :load_structure, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[
$0,
ParallelTests::Tasks.purge_before_load,
"db:structure:load",
"RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
],
args
)
end
desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
task :seed, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[
$0,
"db:seed",
"RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
],
args
)
end
desc "Launch given rake command in parallel"
task :rake, :command, :count do |_, args|
ParallelTests::Tasks.run_in_parallel(
[$0, args.command, "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
args
)
end
['test', 'spec', 'features', 'features-spinach'].each do |type|
desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
task type, [:count, :pattern, :options, :pass_through] do |_t, args|
ParallelTests::Tasks.check_for_pending_migrations
ParallelTests::Tasks.load_lib
command = ParallelTests::Tasks.build_run_command(type, args)
abort unless system(*command) # allow to chain tasks e.g. rake parallel:spec parallel:features
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/test/ 0000775 0000000 0000000 00000000000 15043316274 0021306 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/lib/parallel_tests/test/runner.rb 0000664 0000000 0000000 00000024465 15043316274 0023157 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'shellwords'
require 'parallel_tests'
module ParallelTests
module Test
class Runner
RuntimeLogTooSmallError = Class.new(StandardError)
class << self
# --- usually overwritten by other runners
def runtime_log
'tmp/parallel_runtime_test.log'
end
def test_suffix
/_(test|spec).rb$/
end
def default_test_folder
"test"
end
def test_file_name
"test"
end
def run_tests(test_files, process_number, num_processes, options)
require_list = test_files.map { |file| file.gsub(" ", "\\ ") }.join(" ")
execute_command(build_command(require_list, options), process_number, num_processes, options)
end
# ignores other commands runner noise
def line_is_result?(line)
line =~ /\d+ failure(?!:)/
end
# --- usually used by other runners
# finds all tests and partitions them into groups
def tests_in_groups(tests, num_groups, options = {})
tests = tests_with_size(tests, options)
Grouper.in_even_groups_by_size(tests, num_groups, options)
end
def tests_with_size(tests, options)
tests = find_tests(tests, options)
case options[:group_by]
when :found
tests.map! { |t| [t, 1] }
when :filesize
sort_by_filesize(tests)
when :runtime
sort_by_runtime(
tests, runtimes(tests, options),
options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0)
)
when nil
# use recorded test runtime if we got enough data
runtimes = begin
runtimes(tests, options)
rescue StandardError
[]
end
if runtimes.size * 1.5 > tests.size
puts "Using recorded test runtime" unless options[:quiet]
sort_by_runtime(tests, runtimes)
else
sort_by_filesize(tests)
end
else
raise ArgumentError, "Unsupported option #{options[:group_by]}"
end
tests
end
def execute_command(cmd, process_number, num_processes, options)
number = test_env_number(process_number, options).to_s
env = (options[:env] || {}).merge(
"TEST_ENV_NUMBER" => number,
"PARALLEL_TEST_GROUPS" => num_processes.to_s,
"PARALLEL_PID_FILE" => ParallelTests.pid_file_path
)
cmd = ["nice", *cmd] if options[:nice]
# being able to run with for example `-output foo-$TEST_ENV_NUMBER` worked originally and is convenient
cmd = cmd.map { |c| c.gsub("$TEST_ENV_NUMBER", number).gsub("${TEST_ENV_NUMBER}", number) }
print_command(cmd, env) if report_process_command?(options) && !options[:serialize_stdout]
execute_command_and_capture_output(env, cmd, options)
end
def print_command(command, env)
env_str = ['TEST_ENV_NUMBER', 'PARALLEL_TEST_GROUPS'].map { |e| "#{e}=#{env[e]}" }.join(' ')
puts [env_str, Shellwords.shelljoin(command)].compact.join(' ')
end
def execute_command_and_capture_output(env, cmd, options)
popen_options = {} # do not add `pgroup: true`, it will break `binding.irb` inside the test
popen_options[:err] = [:child, :out] if options[:combine_stderr]
pid = nil
output = IO.popen(env, cmd, popen_options) do |io|
pid = io.pid
ParallelTests.pids.add(pid)
capture_output(io, env, options)
end
ParallelTests.pids.delete(pid) if pid
exitstatus = $?.exitstatus
seed = output[/seed (\d+)/, 1]
output = "#{Shellwords.shelljoin(cmd)}\n#{output}" if report_process_command?(options) && options[:serialize_stdout]
{ env: env, stdout: output, exit_status: exitstatus, command: cmd, seed: seed }
end
def find_results(test_output)
test_output.lines.map do |line|
line.chomp!
line.gsub!(/\e\[\d+m/, '') # remove color coding
next unless line_is_result?(line)
line
end.compact
end
def test_env_number(process_number, options = {})
if process_number == 0 && !options[:first_is_1]
''
else
process_number + 1
end
end
def summarize_results(results)
sums = sum_up_results(results)
sums.sort.map { |word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
end
# remove old seed and add new seed
def command_with_seed(cmd, seed)
clean = remove_command_arguments(cmd, '--seed')
[*clean, '--seed', seed]
end
protected
def executable
if (executable = ENV['PARALLEL_TESTS_EXECUTABLE'])
Shellwords.shellsplit(executable)
else
determine_executable
end
end
def determine_executable
["ruby"]
end
def build_command(file_list, options)
if options[:execute_args]
options[:execute_args] + file_list
else
build_test_command(file_list, options)
end
end
# load all test files, to be overwritten by other runners
def build_test_command(file_list, options)
[
*executable,
'-Itest', # adding ./test directory to the load path for compatibility to common setups
'-e',
"%w[#{file_list}].each { |f| require %{./\#{f}} }", # using %w to keep things readable
'--',
*options[:test_options]
]
end
def sum_up_results(results)
results = results.join(' ').gsub(/s\b/, '') # combine and singularize results
counts = results.scan(/(\d+) (\w+)/)
counts.each_with_object(Hash.new(0)) do |(number, word), sum|
sum[word] += number.to_i
end
end
# read output of the process and print it in chunks
def capture_output(out, env, options = {})
result = +""
begin
loop do
read = out.readpartial(1000000) # read whatever chunk we can get
if Encoding.default_internal
read = read.force_encoding(Encoding.default_internal)
end
result << read
unless options[:serialize_stdout]
message = read
message = "[TEST GROUP #{env['TEST_ENV_NUMBER']}] #{message}" if options[:prefix_output_with_test_env_number]
$stdout.print message
$stdout.flush
end
end
rescue EOFError
nil
end
result
end
def sort_by_runtime(tests, runtimes, options = {})
allowed_missing = options[:allowed_missing] || 1.0
allowed_missing = tests.size * allowed_missing
# set know runtime for each test
tests.sort!
tests.map! do |test|
allowed_missing -= 1 unless time = runtimes[test]
if allowed_missing < 0
log = options[:runtime_log] || runtime_log
raise RuntimeLogTooSmallError, "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update or remove it."
end
[test, time]
end
puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests" if options[:verbose]
set_unknown_runtime tests, options
end
def runtimes(tests, options)
log = options[:runtime_log] || runtime_log
lines = File.read(log).split("\n")
lines.each_with_object({}) do |line, times|
test, _, time = line.rpartition(':')
next unless test && time
times[test] = time.to_f if tests.include?(test)
end
end
def sort_by_filesize(tests)
tests.sort!
tests.map! { |test| [test, File.stat(test).size] }
end
def find_tests(tests, options = {})
suffix_pattern = options[:suffix] || test_suffix
include_pattern = options[:pattern] || //
exclude_pattern = options[:exclude_pattern]
allow_duplicates = options[:allow_duplicates]
files = (tests || []).flat_map do |file_or_folder|
if File.directory?(file_or_folder)
files = files_in_folder(file_or_folder, options)
files = files.grep(suffix_pattern).grep(include_pattern)
files -= files.grep(exclude_pattern) if exclude_pattern
files
else
file_or_folder
end
end
allow_duplicates ? files : files.uniq
end
def files_in_folder(folder, options = {})
pattern = if options[:symlinks] == false # not nil or true
"**/*"
else
# follow one symlink and direct children
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
"**{,/*/**}/*"
end
Dir[File.join(folder, pattern)].uniq.sort
end
def remove_command_arguments(command, *args)
remove_next = false
command.select do |arg|
if remove_next
remove_next = false
false
elsif args.include?(arg)
remove_next = true
false
else
true
end
end
end
private
# fill gaps with unknown-runtime if given, average otherwise
# NOTE: an optimization could be doing runtime by average runtime per file size, but would need file checks
def set_unknown_runtime(tests, options)
known, unknown = tests.partition(&:last)
return if unknown.empty?
unknown_runtime = options[:unknown_runtime] ||
(known.empty? ? 1 : known.map!(&:last).sum / known.size) # average
unknown.each { |set| set[1] = unknown_runtime }
end
def report_process_command?(options)
options[:verbose] || options[:verbose_process_command]
end
end
end
end
end
parallel_tests-5.4.0/lib/parallel_tests/test/runtime_logger.rb 0000664 0000000 0000000 00000004764 15043316274 0024670 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests'
require 'parallel_tests/test/runner'
module ParallelTests
module Test
class RuntimeLogger
@@prepared = false
class << self
def log_test_run(test)
prepare
result = nil
time = ParallelTests.delta { result = yield }
log(test, time)
result
end
def unique_log
with_locked_log do |logfile|
separator = "\n"
groups = logfile.read.split(separator).map { |line| line.split(":") }.group_by(&:first)
lines = groups.map do |file, times|
time = "%.2f" % times.map(&:last).map(&:to_f).sum
"#{file}:#{time}"
end
logfile.rewind
logfile.write(lines.join(separator) + separator)
logfile.truncate(logfile.pos)
end
end
private
def with_locked_log
File.open(logfile, File::RDWR | File::CREAT) do |logfile|
logfile.flock(File::LOCK_EX)
yield logfile
end
end
# ensure folder exists + clean out previous log
# this will happen in multiple processes, but should be roughly at the same time
# so there should be no log message lost
def prepare
return if @@prepared
@@prepared = true
FileUtils.mkdir_p(File.dirname(logfile))
File.write(logfile, '')
end
def log(test, time)
return unless message = message(test, time)
with_locked_log do |logfile|
logfile.seek(0, IO::SEEK_END)
logfile.puts message
end
end
def message(test, delta)
return unless method = test.public_instance_methods(true).detect { |m| m =~ /^test_/ }
filename = test.instance_method(method).source_location.first.sub("#{Dir.pwd}/", "")
"#{filename}:#{delta}"
end
def logfile
ParallelTests::Test::Runner.runtime_log
end
end
end
end
end
if defined?(Minitest::Runnable) # Minitest 5
class << Minitest::Runnable
prepend(
Module.new do
def run(*)
ParallelTests::Test::RuntimeLogger.log_test_run(self) do
super
end
end
end
)
end
class << Minitest
prepend(
Module.new do
def run(*args)
result = super
ParallelTests::Test::RuntimeLogger.unique_log
result
end
end
)
end
end
parallel_tests-5.4.0/lib/parallel_tests/version.rb 0000664 0000000 0000000 00000000113 15043316274 0022334 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module ParallelTests
VERSION = '5.4.0'
end
parallel_tests-5.4.0/parallel_tests.gemspec 0000664 0000000 0000000 00000002022 15043316274 0021122 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
name = "parallel_tests"
require_relative "lib/#{name}/version"
Gem::Specification.new name, ParallelTests::VERSION do |s|
s.summary = "Run Test::Unit / RSpec / Cucumber / Spinach in parallel"
s.authors = ["Michael Grosser"]
s.email = "michael@grosser.it"
s.homepage = "https://github.com/grosser/#{name}"
s.metadata = {
"bug_tracker_uri" => "https://github.com/grosser/#{name}/issues",
"changelog_uri" => "https://github.com/grosser/#{name}/blob/v#{s.version}/CHANGELOG.md",
"documentation_uri" => "https://github.com/grosser/#{name}/blob/v#{s.version}/Readme.md",
"source_code_uri" => "https://github.com/grosser/#{name}/tree/v#{s.version}",
"wiki_uri" => "https://github.com/grosser/#{name}/wiki",
"rubygems_mfa_required" => "true"
}
s.files = Dir["{lib,bin}/**/*"] + ["Readme.md"]
s.license = "MIT"
s.executables = ["parallel_spinach", "parallel_cucumber", "parallel_rspec", "parallel_test"]
s.add_dependency "parallel"
s.required_ruby_version = '>= 3.1.0'
end
parallel_tests-5.4.0/spec/ 0000775 0000000 0000000 00000000000 15043316274 0015475 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/ 0000775 0000000 0000000 00000000000 15043316274 0017346 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/ 0000775 0000000 0000000 00000000000 15043316274 0020631 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/.gitattributes 0000664 0000000 0000000 00000000366 15043316274 0023531 0 ustar 00root root 0000000 0000000 # See https://git-scm.com/docs/gitattributes for more about git attribute files.
# Mark the database schema as having been generated.
db/schema.rb linguist-generated
# Mark any vendored files as having been vendored.
vendor/* linguist-vendored
parallel_tests-5.4.0/spec/fixtures/rails72/.gitignore 0000664 0000000 0000000 00000001412 15043316274 0022617 0 ustar 00root root 0000000 0000000 # See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-*
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
# Ignore uploaded files in development.
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/
!/tmp/storage/.keep
/public/assets
# Ignore master key for decrypting credentials and more.
/config/master.key
parallel_tests-5.4.0/spec/fixtures/rails72/Gemfile 0000664 0000000 0000000 00000001032 15043316274 0022120 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
source "https://rubygems.org"
rails_version = '~> 7.2.0'
gem 'actioncable', rails_version
gem 'actionmailer', rails_version
gem 'actionpack', rails_version
gem 'actionview', rails_version
gem 'activejob', rails_version
gem 'activemodel', rails_version
gem 'activerecord', rails_version
gem 'activesupport', rails_version
gem 'railties', rails_version
gem 'sqlite3', '~> 1.7.3' # last before 2.0 which has weird install errors
gem 'tzinfo-data'
gem 'parallel_tests', path: "../../../", group: :development
parallel_tests-5.4.0/spec/fixtures/rails72/Gemfile.lock 0000664 0000000 0000000 00000010047 15043316274 0023055 0 ustar 00root root 0000000 0000000 PATH
remote: ../../..
specs:
parallel_tests (5.4.0)
parallel
GEM
remote: https://rubygems.org/
specs:
actioncable (7.2.2.1)
actionpack (= 7.2.2.1)
activesupport (= 7.2.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailer (7.2.2.1)
actionpack (= 7.2.2.1)
actionview (= 7.2.2.1)
activejob (= 7.2.2.1)
activesupport (= 7.2.2.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.2.2.1)
actionview (= 7.2.2.1)
activesupport (= 7.2.2.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actionview (7.2.2.1)
activesupport (= 7.2.2.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.2.2.1)
activesupport (= 7.2.2.1)
globalid (>= 0.3.6)
activemodel (7.2.2.1)
activesupport (= 7.2.2.1)
activerecord (7.2.2.1)
activemodel (= 7.2.2.1)
activesupport (= 7.2.2.1)
timeout (>= 0.4.0)
activesupport (7.2.2.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
builder (3.3.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
crass (1.0.6)
date (3.4.1)
drb (2.2.1)
erubi (1.13.1)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
io-console (0.8.0)
irb (1.15.1)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
logger (1.6.6)
loofah (2.24.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
mini_mime (1.1.5)
mini_portile2 (2.8.8)
minitest (5.25.4)
net-imap (0.5.6)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.3-arm64-darwin)
racc (~> 1.4)
parallel (1.26.3)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
psych (5.2.3)
date
stringio
racc (1.8.1)
rack (3.1.10)
rack-session (2.1.0)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rackup (2.2.1)
rack (>= 3)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (7.2.2.1)
actionpack (= 7.2.2.1)
activesupport (= 7.2.2.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rake (13.2.1)
rdoc (6.12.0)
psych (>= 4.0.0)
reline (0.6.0)
io-console (~> 0.5)
securerandom (0.4.1)
sqlite3 (1.7.3)
mini_portile2 (~> 2.8.0)
stringio (3.1.5)
thor (1.3.2)
timeout (0.4.3)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.1)
tzinfo (>= 1.0.0)
useragent (0.16.11)
websocket-driver (0.7.7)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
zeitwerk (2.7.2)
PLATFORMS
arm64-darwin-23
ruby
DEPENDENCIES
actioncable (~> 7.2.0)
actionmailer (~> 7.2.0)
actionpack (~> 7.2.0)
actionview (~> 7.2.0)
activejob (~> 7.2.0)
activemodel (~> 7.2.0)
activerecord (~> 7.2.0)
activesupport (~> 7.2.0)
parallel_tests!
railties (~> 7.2.0)
sqlite3 (~> 1.7.3)
tzinfo-data
BUNDLED WITH
2.6.2
parallel_tests-5.4.0/spec/fixtures/rails72/README.md 0000664 0000000 0000000 00000000566 15043316274 0022117 0 ustar 00root root 0000000 0000000 # README
This README would normally document whatever steps are necessary to get the
application up and running.
Things you may want to cover:
* Ruby version
* System dependencies
* Configuration
* Database creation
* Database initialization
* How to run the test suite
* Services (job queues, cache servers, search engines, etc.)
* Deployment instructions
* ...
parallel_tests-5.4.0/spec/fixtures/rails72/Rakefile 0000664 0000000 0000000 00000000343 15043316274 0022276 0 ustar 00root root 0000000 0000000 # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application"
Rails.application.load_tasks
parallel_tests-5.4.0/spec/fixtures/rails72/app/ 0000775 0000000 0000000 00000000000 15043316274 0021411 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/ 0000775 0000000 0000000 00000000000 15043316274 0022713 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/config/ 0000775 0000000 0000000 00000000000 15043316274 0024160 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/config/manifest.js 0000664 0000000 0000000 00000000217 15043316274 0026324 0 ustar 00root root 0000000 0000000 //= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/images/ 0000775 0000000 0000000 00000000000 15043316274 0024160 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/images/.keep 0000664 0000000 0000000 00000000000 15043316274 0025073 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/stylesheets/ 0000775 0000000 0000000 00000000000 15043316274 0025267 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/assets/stylesheets/application.css 0000664 0000000 0000000 00000001321 15043316274 0030301 0 ustar 00root root 0000000 0000000 /*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
parallel_tests-5.4.0/spec/fixtures/rails72/app/channels/ 0000775 0000000 0000000 00000000000 15043316274 0023204 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/channels/application_cable/ 0000775 0000000 0000000 00000000000 15043316274 0026635 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/channels/application_cable/channel.rb 0000664 0000000 0000000 00000000117 15043316274 0030571 0 ustar 00root root 0000000 0000000 module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/channels/application_cable/connection.rb 0000664 0000000 0000000 00000000125 15043316274 0031317 0 ustar 00root root 0000000 0000000 module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/controllers/ 0000775 0000000 0000000 00000000000 15043316274 0023757 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/controllers/application_controller.rb 0000664 0000000 0000000 00000000071 15043316274 0031050 0 ustar 00root root 0000000 0000000 class ApplicationController < ActionController::Base
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/controllers/concerns/ 0000775 0000000 0000000 00000000000 15043316274 0025571 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/controllers/concerns/.keep 0000664 0000000 0000000 00000000000 15043316274 0026504 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/helpers/ 0000775 0000000 0000000 00000000000 15043316274 0023053 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/helpers/application_helper.rb 0000664 0000000 0000000 00000000035 15043316274 0027240 0 ustar 00root root 0000000 0000000 module ApplicationHelper
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/javascript/ 0000775 0000000 0000000 00000000000 15043316274 0023557 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/javascript/application.js 0000664 0000000 0000000 00000000235 15043316274 0026420 0 ustar 00root root 0000000 0000000 // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
parallel_tests-5.4.0/spec/fixtures/rails72/app/javascript/controllers/ 0000775 0000000 0000000 00000000000 15043316274 0026125 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/javascript/controllers/application.js 0000664 0000000 0000000 00000000332 15043316274 0030764 0 ustar 00root root 0000000 0000000 import { Application } from "@hotwired/stimulus"
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
export { application }
parallel_tests-5.4.0/spec/fixtures/rails72/app/javascript/controllers/hello_controller.js 0000664 0000000 0000000 00000000235 15043316274 0032031 0 ustar 00root root 0000000 0000000 import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
this.element.textContent = "Hello World!"
}
}
parallel_tests-5.4.0/spec/fixtures/rails72/app/javascript/controllers/index.js 0000664 0000000 0000000 00000001110 15043316274 0027563 0 ustar 00root root 0000000 0000000 // Import and register all your controllers from the importmap under controllers/*
import { application } from "controllers/application"
// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)
parallel_tests-5.4.0/spec/fixtures/rails72/app/jobs/ 0000775 0000000 0000000 00000000000 15043316274 0022346 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/jobs/application_job.rb 0000664 0000000 0000000 00000000415 15043316274 0026030 0 ustar 00root root 0000000 0000000 class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/mailers/ 0000775 0000000 0000000 00000000000 15043316274 0023045 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/mailers/application_mailer.rb 0000664 0000000 0000000 00000000146 15043316274 0027227 0 ustar 00root root 0000000 0000000 class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout "mailer"
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/models/ 0000775 0000000 0000000 00000000000 15043316274 0022674 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/models/application_record.rb 0000664 0000000 0000000 00000000112 15043316274 0027054 0 ustar 00root root 0000000 0000000 class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/models/concerns/ 0000775 0000000 0000000 00000000000 15043316274 0024506 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/models/concerns/.keep 0000664 0000000 0000000 00000000000 15043316274 0025421 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/models/user.rb 0000664 0000000 0000000 00000000043 15043316274 0024174 0 ustar 00root root 0000000 0000000 class User < ApplicationRecord
end
parallel_tests-5.4.0/spec/fixtures/rails72/app/views/ 0000775 0000000 0000000 00000000000 15043316274 0022546 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/views/layouts/ 0000775 0000000 0000000 00000000000 15043316274 0024246 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/app/views/layouts/application.html.erb 0000664 0000000 0000000 00000000535 15043316274 0030211 0 ustar 00root root 0000000 0000000
Rails70
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= yield %>
parallel_tests-5.4.0/spec/fixtures/rails72/app/views/layouts/mailer.html.erb 0000664 0000000 0000000 00000000345 15043316274 0027156 0 ustar 00root root 0000000 0000000
<%= yield %>
parallel_tests-5.4.0/spec/fixtures/rails72/app/views/layouts/mailer.text.erb 0000664 0000000 0000000 00000000015 15043316274 0027170 0 ustar 00root root 0000000 0000000 <%= yield %>
parallel_tests-5.4.0/spec/fixtures/rails72/bin/ 0000775 0000000 0000000 00000000000 15043316274 0021401 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/bin/bundle 0000775 0000000 0000000 00000005617 15043316274 0022611 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "rubygems"
m = Module.new do
module_function
def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__)
end
def env_var_version
ENV["BUNDLER_VERSION"]
end
def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = Regexp.last_match(1)
update_index = i
end
bundler_version
end
def gemfile
gemfile = ENV["BUNDLE_GEMFILE"]
return gemfile if gemfile && !gemfile.empty?
File.expand_path('../Gemfile', __dir__)
end
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
end
def lockfile_version
return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1)
end
def bundler_version
@bundler_version ||=
env_var_version || cli_arg_version ||
lockfile_version
end
def bundler_requirement
return "#{Gem::Requirement.default}.a" unless bundler_version
bundler_gem_version = Gem::Version.new(bundler_version)
requirement = bundler_gem_version.approximate_recommendation
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
requirement += ".a" if bundler_gem_version.prerelease?
requirement
end
def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile
activate_bundler
end
def activate_bundler
gem_error = activation_error_handling do
gem "bundler", bundler_requirement
end
return if gem_error.nil?
require_error = activation_error_handling do
require "bundler/version"
end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42
end
def activation_error_handling
yield
nil
rescue StandardError, LoadError => e
e
end
end
m.load_bundler!
if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end
parallel_tests-5.4.0/spec/fixtures/rails72/bin/importmap 0000775 0000000 0000000 00000000133 15043316274 0023334 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
require_relative "../config/application"
require "importmap/commands"
parallel_tests-5.4.0/spec/fixtures/rails72/bin/rails 0000775 0000000 0000000 00000000215 15043316274 0022437 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
parallel_tests-5.4.0/spec/fixtures/rails72/bin/rake 0000775 0000000 0000000 00000000132 15043316274 0022245 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run
parallel_tests-5.4.0/spec/fixtures/rails72/bin/setup 0000775 0000000 0000000 00000001762 15043316274 0022475 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
require "fileutils"
# path to your application root.
APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
FileUtils.chdir APP_ROOT do
# This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
system! "bin/rails db:prepare"
puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server =="
system! "bin/rails restart"
end
parallel_tests-5.4.0/spec/fixtures/rails72/config.ru 0000664 0000000 0000000 00000000240 15043316274 0022442 0 ustar 00root root 0000000 0000000 # This file is used by Rack-based servers to start the application.
require_relative "config/environment"
run Rails.application
Rails.application.load_server
parallel_tests-5.4.0/spec/fixtures/rails72/config/ 0000775 0000000 0000000 00000000000 15043316274 0022076 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/config/application.rb 0000664 0000000 0000000 00000002133 15043316274 0024725 0 ustar 00root root 0000000 0000000 require_relative "boot"
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
# require "action_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Rails70
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
end
end
parallel_tests-5.4.0/spec/fixtures/rails72/config/boot.rb 0000664 0000000 0000000 00000000200 15043316274 0023356 0 ustar 00root root 0000000 0000000 ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" # Set up gems listed in the Gemfile.
parallel_tests-5.4.0/spec/fixtures/rails72/config/cable.yml 0000664 0000000 0000000 00000000274 15043316274 0023672 0 ustar 00root root 0000000 0000000 development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: rails70_production
parallel_tests-5.4.0/spec/fixtures/rails72/config/credentials.yml.enc 0000664 0000000 0000000 00000000720 15043316274 0025661 0 ustar 00root root 0000000 0000000 Hf5WtG5Ycg/SndPHUS0iSmW45dzUSvrchVGgjvSrvDCEcaa8D3tsTh/VFmTP8Gv75jVJplktDXAQxQC9D3BHrMAn8f0vjgG0iqpdnqWSmextXZPYePg6UGCfCWQ+VTFH/SY+epnO7Aa9VER0Ln5CVFRAURrxUNxqbXYIPQElgAdmGnI2LGJtoH/yyOq8cklCCt4bT5jzjiTMhEaA/o6kqLA2gN94oYoHGqsQZTYvo4Jv2S+gWiPOlbGEQbgOtEtnsy4akPpzjOxcGWGqP9cUDZtmO0fIxPI4OjXmlKmdzGa+UWvmWiku8hEu/rdhHU0kn8jqGh5Yoz7jgHY/XQyrRWSqNln+2rS3KwRcJ2dOgx8Nh7vV+zIgb0bQX0ZRGH5AdFdOoVdZaPFjSZIi0YWleXeuGvtfiTZuVpGc--EizbcaQddFxMXHC2--oZbQNGBHn4CVXLXLazOMdw== parallel_tests-5.4.0/spec/fixtures/rails72/config/database.yml 0000664 0000000 0000000 00000001211 15043316274 0024360 0 ustar 00root root 0000000 0000000 # SQLite. Versions 3.8.0 and up are supported.
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem "sqlite3"
#
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: db/development.sqlite3
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: db/test<%= ENV['TEST_ENV_NUMBER'] %>.sqlite3
production:
<<: *default
database: db/production.sqlite3
parallel_tests-5.4.0/spec/fixtures/rails72/config/environment.rb 0000664 0000000 0000000 00000000200 15043316274 0024757 0 ustar 00root root 0000000 0000000 # Load the Rails application.
require_relative "application"
# Initialize the Rails application.
Rails.application.initialize!
parallel_tests-5.4.0/spec/fixtures/rails72/config/environments/ 0000775 0000000 0000000 00000000000 15043316274 0024625 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/config/environments/development.rb 0000664 0000000 0000000 00000004557 15043316274 0027507 0 ustar 00root root 0000000 0000000 require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded any time
# it changes. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports.
config.consider_all_requests_local = true
# Enable server timing
config.server_timing = true
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=#{2.days.to_i}"
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# Store uploaded files on the local file system (see config/storage.yml for options).
# config.active_storage.service = :local
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Suppress logger output for asset requests.
# config.assets.quiet = true
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
end
parallel_tests-5.4.0/spec/fixtures/rails72/config/environments/production.rb 0000664 0000000 0000000 00000007467 15043316274 0027356 0 ustar 00root root 0000000 0000000 require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
# Compress CSS using a preprocessor.
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
# config.assets.compile = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com"
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
# config.active_storage.service = :local
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = "wss://example.com/cable"
# config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Include generic and useful information about system operation, but avoid logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII).
config.log_level = :info
# Prepend all log lines with the following tags.
config.log_tags = [:request_id]
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "rails70_production"
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require "syslog/logger"
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
end
parallel_tests-5.4.0/spec/fixtures/rails72/config/environments/test.rb 0000664 0000000 0000000 00000004525 15043316274 0026137 0 ustar 00root root 0000000 0000000 require "active_support/core_ext/integer/time"
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Turn false under Spring and add config.action_view.cache_template_loading = true.
config.cache_classes = true
# Eager loading loads your whole application. When running a single test locally,
# this probably isn't necessary. It's a good idea to do in a continuous integration
# system, or in some way before deploying your code.
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=#{1.hour.to_i}"
}
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory.
# config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
end
parallel_tests-5.4.0/spec/fixtures/rails72/config/importmap.rb 0000664 0000000 0000000 00000000531 15043316274 0024432 0 ustar 00root root 0000000 0000000 # Pin npm packages by running ./bin/importmap
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
parallel_tests-5.4.0/spec/fixtures/rails72/config/initializers/ 0000775 0000000 0000000 00000000000 15043316274 0024604 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/config/initializers/assets.rb 0000664 0000000 0000000 00000000770 15043316274 0026437 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
# Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
parallel_tests-5.4.0/spec/fixtures/rails72/config/initializers/content_security_policy.rb 0000664 0000000 0000000 00000002164 15043316274 0032114 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# Rails.application.configure do
# config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
#
# # Generate session nonces for permitted importmap and inline scripts
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
# config.content_security_policy_nonce_directives = %w(script-src)
#
# # Report CSP violations to a specified URI. See:
# # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# # config.content_security_policy_report_only = true
# end
parallel_tests-5.4.0/spec/fixtures/rails72/config/initializers/filter_parameter_logging.rb 0000664 0000000 0000000 00000000614 15043316274 0032165 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Configure parameters to be filtered from the log file. Use this to limit dissemination of
# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
# notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
]
parallel_tests-5.4.0/spec/fixtures/rails72/config/initializers/inflections.rb 0000664 0000000 0000000 00000001211 15043316274 0027441 0 ustar 00root root 0000000 0000000 # Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, "\\1en"
# inflect.singular /^(ox)en/i, "\\1"
# inflect.irregular "person", "people"
# inflect.uncountable %w( fish sheep )
# end
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym "RESTful"
# end
parallel_tests-5.4.0/spec/fixtures/rails72/config/initializers/permissions_policy.rb 0000664 0000000 0000000 00000000600 15043316274 0031057 0 ustar 00root root 0000000 0000000 # Define an application-wide HTTP permissions policy. For further
# information see https://developers.google.com/web/updates/2018/06/feature-policy
#
# Rails.application.config.permissions_policy do |f|
# f.camera :none
# f.gyroscope :none
# f.microphone :none
# f.usb :none
# f.fullscreen :self
# f.payment :self, "https://secure.example.com"
# end
parallel_tests-5.4.0/spec/fixtures/rails72/config/locales/ 0000775 0000000 0000000 00000000000 15043316274 0023520 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/config/locales/en.yml 0000664 0000000 0000000 00000001521 15043316274 0024644 0 ustar 00root root 0000000 0000000 # Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
# <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# The following keys must be escaped otherwise they will not be retrieved by
# the default I18n backend:
#
# true, false, on, off, yes, no
#
# Instead, surround them with single quotes.
#
# en:
# "true": "foo"
#
# To learn more, please read the Rails Internationalization guide
# available at https://guides.rubyonrails.org/i18n.html.
en:
hello: "Hello world"
parallel_tests-5.4.0/spec/fixtures/rails72/config/puma.rb 0000664 0000000 0000000 00000003372 15043316274 0023372 0 ustar 00root root 0000000 0000000 # Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
#
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port ENV.fetch("PORT", 3000)
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
parallel_tests-5.4.0/spec/fixtures/rails72/config/routes.rb 0000664 0000000 0000000 00000000304 15043316274 0023741 0 ustar 00root root 0000000 0000000 Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
# root "articles#index"
end
parallel_tests-5.4.0/spec/fixtures/rails72/config/storage.yml 0000664 0000000 0000000 00000002200 15043316274 0024257 0 ustar 00root root 0000000 0000000 test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
# service: S3
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
# region: us-east-1
# bucket: your_own_bucket-<%= Rails.env %>
# Remember not to checkin your GCS keyfile to a repository
# google:
# service: GCS
# project: your_project
# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
# bucket: your_own_bucket-<%= Rails.env %>
# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
# service: AzureStorage
# storage_account_name: your_account_name
# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
# container: your_container_name-<%= Rails.env %>
# mirror:
# service: Mirror
# primary: local
# mirrors: [ amazon, google, microsoft ]
parallel_tests-5.4.0/spec/fixtures/rails72/db/ 0000775 0000000 0000000 00000000000 15043316274 0021216 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/db/migrate/ 0000775 0000000 0000000 00000000000 15043316274 0022646 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/db/migrate/20220322153446_create_users.rb 0000664 0000000 0000000 00000000230 15043316274 0027315 0 ustar 00root root 0000000 0000000 class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
parallel_tests-5.4.0/spec/fixtures/rails72/db/schema.rb 0000664 0000000 0000000 00000001645 15043316274 0023011 0 ustar 00root root 0000000 0000000 # This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2022_03_22_153446) do
create_table "users", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
parallel_tests-5.4.0/spec/fixtures/rails72/db/seeds.rb 0000664 0000000 0000000 00000000566 15043316274 0022655 0 ustar 00root root 0000000 0000000 # This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
# Character.create(name: "Luke", movie: movies.first)
parallel_tests-5.4.0/spec/fixtures/rails72/lib/ 0000775 0000000 0000000 00000000000 15043316274 0021377 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/lib/assets/ 0000775 0000000 0000000 00000000000 15043316274 0022701 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/lib/assets/.keep 0000664 0000000 0000000 00000000000 15043316274 0023614 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/lib/tasks/ 0000775 0000000 0000000 00000000000 15043316274 0022524 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/lib/tasks/.keep 0000664 0000000 0000000 00000000000 15043316274 0023437 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/public/ 0000775 0000000 0000000 00000000000 15043316274 0022107 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/public/404.html 0000664 0000000 0000000 00000003272 15043316274 0023310 0 ustar 00root root 0000000 0000000
The page you were looking for doesn't exist (404)
The page you were looking for doesn't exist.
You may have mistyped the address or the page may have moved.
If you are the application owner check the logs for more information.
parallel_tests-5.4.0/spec/fixtures/rails72/public/422.html 0000664 0000000 0000000 00000003251 15043316274 0023305 0 ustar 00root root 0000000 0000000
The change you wanted was rejected (422)
The change you wanted was rejected.
Maybe you tried to change something you didn't have access to.
If you are the application owner check the logs for more information.
parallel_tests-5.4.0/spec/fixtures/rails72/public/500.html 0000664 0000000 0000000 00000003143 15043316274 0023302 0 ustar 00root root 0000000 0000000
We're sorry, but something went wrong (500)
We're sorry, but something went wrong.
If you are the application owner check the logs for more information.
parallel_tests-5.4.0/spec/fixtures/rails72/public/apple-touch-icon-precomposed.png 0000664 0000000 0000000 00000000000 15043316274 0030270 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/public/apple-touch-icon.png 0000664 0000000 0000000 00000000000 15043316274 0025752 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/public/favicon.ico 0000664 0000000 0000000 00000000000 15043316274 0024216 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/public/robots.txt 0000664 0000000 0000000 00000000143 15043316274 0024156 0 ustar 00root root 0000000 0000000 # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
parallel_tests-5.4.0/spec/fixtures/rails72/storage/ 0000775 0000000 0000000 00000000000 15043316274 0022275 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/storage/.keep 0000664 0000000 0000000 00000000000 15043316274 0023210 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/ 0000775 0000000 0000000 00000000000 15043316274 0021610 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/application_system_test_case.rb 0000664 0000000 0000000 00000000235 15043316274 0030076 0 ustar 00root root 0000000 0000000 require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
parallel_tests-5.4.0/spec/fixtures/rails72/test/channels/ 0000775 0000000 0000000 00000000000 15043316274 0023403 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/channels/application_cable/ 0000775 0000000 0000000 00000000000 15043316274 0027034 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/channels/application_cable/connection_test.rb 0000664 0000000 0000000 00000000370 15043316274 0032557 0 ustar 00root root 0000000 0000000 require "test_helper"
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
# test "connects with cookies" do
# cookies.signed[:user_id] = 42
#
# connect
#
# assert_equal connection.user_id, "42"
# end
end
parallel_tests-5.4.0/spec/fixtures/rails72/test/controllers/ 0000775 0000000 0000000 00000000000 15043316274 0024156 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/controllers/.keep 0000664 0000000 0000000 00000000000 15043316274 0025071 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/fixtures/ 0000775 0000000 0000000 00000000000 15043316274 0023461 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/fixtures/files/ 0000775 0000000 0000000 00000000000 15043316274 0024563 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/fixtures/files/.keep 0000664 0000000 0000000 00000000000 15043316274 0025476 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/fixtures/users.yml 0000664 0000000 0000000 00000000542 15043316274 0025346 0 ustar 00root root 0000000 0000000 # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
# This model initially had no columns defined. If you add columns to the
# model remove the "{}" from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
# column: value
parallel_tests-5.4.0/spec/fixtures/rails72/test/helpers/ 0000775 0000000 0000000 00000000000 15043316274 0023252 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/helpers/.keep 0000664 0000000 0000000 00000000000 15043316274 0024165 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/integration/ 0000775 0000000 0000000 00000000000 15043316274 0024133 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/integration/.keep 0000664 0000000 0000000 00000000000 15043316274 0025046 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/mailers/ 0000775 0000000 0000000 00000000000 15043316274 0023244 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/mailers/.keep 0000664 0000000 0000000 00000000000 15043316274 0024157 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/models/ 0000775 0000000 0000000 00000000000 15043316274 0023073 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/models/.keep 0000664 0000000 0000000 00000000000 15043316274 0024006 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/models/user_test.rb 0000664 0000000 0000000 00000000166 15043316274 0025440 0 ustar 00root root 0000000 0000000 require "test_helper"
class UserTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
parallel_tests-5.4.0/spec/fixtures/rails72/test/system/ 0000775 0000000 0000000 00000000000 15043316274 0023134 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/system/.keep 0000664 0000000 0000000 00000000000 15043316274 0024047 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/test/test_helper.rb 0000664 0000000 0000000 00000000602 15043316274 0024451 0 ustar 00root root 0000000 0000000 ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
parallel_tests-5.4.0/spec/fixtures/rails72/vendor/ 0000775 0000000 0000000 00000000000 15043316274 0022126 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/vendor/.keep 0000664 0000000 0000000 00000000000 15043316274 0023041 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/vendor/javascript/ 0000775 0000000 0000000 00000000000 15043316274 0024274 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/fixtures/rails72/vendor/javascript/.keep 0000664 0000000 0000000 00000000000 15043316274 0025207 0 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/integration_spec.rb 0000664 0000000 0000000 00000075663 15043316274 0021400 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe 'CLI' do
before do
FileUtils.remove_dir(folder, true)
end
after do
FileUtils.remove_dir(folder, true)
end
def folder
"/tmp/parallel_tests_tests"
end
def write(file, content)
path = "#{folder}/#{file}"
FileUtils.mkpath File.dirname(path)
File.write(path, content)
path
end
def read(file)
File.read "#{folder}/#{file}"
end
def bin_folder
File.expand_path('../bin', __dir__)
end
def executable(options = {})
["ruby", "#{bin_folder}/parallel_#{options[:type] || 'test'}"]
end
def run_tests(test_folder, options = {})
FileUtils.mkpath folder
command = [*executable(options), *test_folder]
command += ["-n", (options[:processes] || 2).to_s] unless options[:processes] == false
command += options[:add] if options[:add]
result = ''
Dir.chdir(folder) do
env = options[:export] || {}
IO.popen(env, command, err: [:child, :out]) do |io|
yield(io) if block_given?
result = io.read
end
end
raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
result
end
def self.it_runs_the_default_folder_if_it_exists(type, test_folder)
it "runs the default folder if it exists" do
full_path_to_test_folder = File.join(folder, test_folder)
FileUtils.mkpath full_path_to_test_folder
results = run_tests(nil, fail: false, type: type)
expect(results).to_not include("Pass files or folders to run")
FileUtils.remove_dir(full_path_to_test_folder, true)
results = run_tests(nil, fail: true, type: type)
expect(results).to include("Pass files or folders to run")
end
end
let(:printed_commands) { /specs? per process\nTEST_ENV_NUMBER=(\d+)? PARALLEL_TEST_GROUPS=\d+ bundle exec rspec/ }
let(:printed_rerun) { /run the group again:\n\nTEST_ENV_NUMBER=(\d+)? PARALLEL_TEST_GROUPS=\d+ bundle exec rspec/ }
context "running tests sequentially" do
it "exits with 0 when each run is successful" do
run_tests "spec", type: 'rspec', fail: 0
end
it "exits with 1 when a test run fails" do
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){ expect(true).to be false }}'
run_tests "spec", type: 'rspec', fail: 1
end
it "exits with 1 even when the test run exits with a different status" do
write 'spec/xxx2_spec.rb', <<~SPEC
RSpec.configure { |c| c.failure_exit_code = 99 }
describe("it"){it("should"){ expect(true).to be false }}
SPEC
run_tests "spec", type: 'rspec', fail: 1
end
it "exits with the highest exit status" do
write 'spec/xxx2_spec.rb', <<~SPEC
RSpec.configure { |c| c.failure_exit_code = 99 }
describe("it"){it("should"){ expect(true).to be false }}
SPEC
run_tests "spec", type: 'rspec', add: ["--highest-exit-status"], fail: 99
end
end
it "runs tests in parallel" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
# set processes to false so we verify empty groups are discarded by default
result = run_tests "spec", type: 'rspec', processes: 4
# test ran and gave their puts
expect(result).to include('TEST1')
expect(result).to include('TEST2')
# all results present
expect(result).to include_exactly_times('1 example, 0 failure', 2) # 2 results
expect(result).to include_exactly_times('2 examples, 0 failures', 1) # 1 summary
expect(result).to include_exactly_times(/Finished in \d+(\.\d+)? seconds/, 2)
expect(result).to include_exactly_times(/Took \d+ seconds/, 1) # parallel summary
# verify empty groups are discarded. if retained then it'd say 4 processes for 2 specs
expect(result).to include '2 processes for 2 specs, ~ 1 spec per process'
end
context "running test in parallel" do
it "exits with 0 when each run is successful" do
run_tests "spec", type: 'rspec', processes: 4, fail: 0
end
it "exits with 1 when a test run fails" do
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){ expect(true).to be false }}'
run_tests "spec", type: 'rspec', processes: 4, fail: 1
end
it "exits with 1 even when the test run exits with a different status" do
write 'spec/xxx2_spec.rb', <<~SPEC
RSpec.configure { |c| c.failure_exit_code = 99 }
describe("it"){it("should"){ expect(true).to be false }}
SPEC
run_tests "spec", type: 'rspec', processes: 4, fail: 1
end
it "exits with the highest exit status" do
write 'spec/xxx2_spec.rb', <<~SPEC
RSpec.configure { |c| c.failure_exit_code = 99 }
describe("it"){it("should"){ expect(true).to be false }}
SPEC
run_tests "spec", type: 'rspec', processes: 4, add: ["--highest-exit-status"], fail: 99
end
end
# Uses `Process.kill` under the hood, which on Windows doesn't work as expected. It kills all process group instead of just one process.
describe "--fail-fast", unless: Gem.win_platform? do
def run_tests(test_option: nil)
# group-by + order for stable execution ... doc and verbose to ease debugging
test_options = "--format doc --order defined"
test_options = "#{test_options} #{test_option}" if test_option
super(
"spec",
fail: true,
type: 'rspec',
processes: 2,
add: ["--group-by", "found", "--verbose", "--fail-fast", "--test-options", test_options]
)
end
before do
write 'spec/xxx1_spec.rb', 'describe("T1"){it("E1"){puts "YE" + "S"; sleep 0.5; expect(1).to eq(2)}}' # group 1 executed
write 'spec/xxx2_spec.rb', 'describe("T2"){it("E2"){sleep 1; puts "OK"}}' # group 2 executed
write 'spec/xxx3_spec.rb', 'describe("T3"){it("E3"){puts "NO3"}}' # group 1 skipped
write 'spec/xxx4_spec.rb', 'describe("T4"){it("E4"){puts "NO4"}}' # group 2 skipped
write 'spec/xxx5_spec.rb', 'describe("T5"){it("E5"){puts "NO5"}}' # group 1 skipped
write 'spec/xxx6_spec.rb', 'describe("T6"){it("E6"){puts "NO6"}}' # group 2 skipped
end
it "can fail fast on a single test" do
result = run_tests(test_option: "--fail-fast")
expect(result).to include_exactly_times("YES", 1)
expect(result).to include_exactly_times("OK", 1) # is allowed to finish but no new test is started after
expect(result).to_not include("NO")
expect(result).to include_exactly_times('1 example, 1 failure', 1) # rspec group 1
expect(result).to include_exactly_times('1 example, 0 failure', 1) # rspec group 2
expect(result).to include_exactly_times('2 examples, 1 failure', 1) # parallel_rspec summary
expect(result).to include '2 processes for 6 specs, ~ 3 specs per process'
end
it "can fail fast on a single group" do
result = run_tests
expect(result).to include_exactly_times("YES", 1)
expect(result).to include_exactly_times("OK", 1) # is allowed to finish but no new test is started after
expect(result).to include_exactly_times("NO", 2)
expect(result).to include_exactly_times('3 examples, 1 failure', 1) # rspec group 1
expect(result).to include_exactly_times('1 example, 0 failure', 1) # rspec group 2
expect(result).to include_exactly_times('4 examples, 1 failure', 1) # parallel_rspec summary
expect(result).to include '2 processes for 6 specs, ~ 3 specs per process'
end
end
it "runs tests which outputs accented characters" do
write "spec/xxx_spec.rb", "#encoding: utf-8\ndescribe('it'){it('should'){puts 'Byłem tu'}}"
result = run_tests "spec", type: 'rspec'
# test ran and gave their puts
expect(result).to include('Byłem tu')
end
it "respects default encoding when reading child stdout" do
write 'test/xxx_test.rb', <<-EOF
require 'test/unit'
class XTest < Test::Unit::TestCase
def test_unicode
raise '¯\\_(ツ)_/¯'
end
end
EOF
# Need to tell Ruby to default to utf-8 to simulate environments where
# this is set. (Otherwise, it defaults to nil and the undefined conversion
# issue doesn't come up.)
result = run_tests('test', fail: true, export: { 'RUBYOPT' => 'Eutf-8:utf-8' })
expect(result).to include('¯\_(ツ)_/¯')
end
it "does not run any tests if there are none" do
write 'spec/xxx_spec.rb', '1'
result = run_tests "spec", type: 'rspec'
expect(result).to include('No examples found')
expect(result).to include('Took')
end
it "shows command and rerun with --verbose-command" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){expect(1).to eq(2)}}'
result = run_tests ["spec", "--verbose-command"], type: 'rspec', fail: true
expect(result).to match printed_commands
expect(result).to match printed_rerun
expect(result).to include "bundle exec rspec spec/xxx_spec.rb"
expect(result).to include "bundle exec rspec spec/xxx2_spec.rb"
end
it "shows only rerun with --verbose-rerun-command" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){expect(1).to eq(2)}}'
result = run_tests ["spec", "--verbose-rerun-command"], type: 'rspec', fail: true
expect(result).to match printed_rerun
expect(result).to_not match printed_commands
end
it "shows only process with --verbose-process-command" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){expect(1).to eq(2)}}'
result = run_tests ["spec", "--verbose-process-command"], type: 'rspec', fail: true
expect(result).to_not match printed_rerun
expect(result).to match printed_commands
end
it "fails when tests fail" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){expect(1).to eq(2)}}'
result = run_tests "spec", fail: true, type: 'rspec'
expect(result).to include_exactly_times('1 example, 1 failure', 1)
expect(result).to include_exactly_times('1 example, 0 failure', 1)
expect(result).to include_exactly_times('2 examples, 1 failure', 1)
end
it "can serialize stdout" do
write 'spec/xxx_spec.rb', '5.times{describe("it"){it("should"){sleep 0.01; puts "TEST1"}}}'
write 'spec/xxx2_spec.rb', 'sleep 0.01; 5.times{describe("it"){it("should"){sleep 0.01; puts "TEST2"}}}'
result = run_tests "spec", type: 'rspec', add: ["--serialize-stdout"]
expect(result).not_to match(/TEST1.*TEST2.*TEST1/m)
expect(result).not_to match(/TEST2.*TEST1.*TEST2/m)
end
it "can show simulated output when serializing stdout" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){sleep 0.5; puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){sleep 1; puts "TEST2"}}'
result = run_tests "spec", type: 'rspec', add: ["--serialize-stdout"], export: { 'PARALLEL_TEST_HEARTBEAT_INTERVAL' => '0.01' }
expect(result).to match(/\.{4}.*TEST1.*\.{4}.*TEST2/m)
end
it "can show simulated output preceded by command when serializing stdout with verbose option" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){sleep 1.5; puts "TEST1"}}'
result = run_tests ["spec", "--verbose"], type: 'rspec', add: ["--serialize-stdout"], export: { 'PARALLEL_TEST_HEARTBEAT_INTERVAL' => '0.02' }
expect(result).to match(/\.{5}.*\nbundle exec rspec spec\/xxx_spec\.rb\n.*^TEST1/m)
end
it "can serialize stdout and stderr" do
write 'spec/xxx_spec.rb', '5.times{describe("it"){it("should"){sleep 0.01; $stderr.puts "errTEST1"; puts "TEST1"}}}'
write 'spec/xxx2_spec.rb', 'sleep 0.01; 5.times{describe("it"){it("should"){sleep 0.01; $stderr.puts "errTEST2"; puts "TEST2"}}}'
result = run_tests ["spec"], type: 'rspec', add: ["--serialize-stdout", "--combine-stderr"]
expect(result).not_to match(/TEST1.*TEST2.*TEST1/m)
expect(result).not_to match(/TEST2.*TEST1.*TEST2/m)
end
context "with given commands" do
it "can exec given commands with ENV['TEST_ENV_NUMBER']" do
result = run_tests ['-e', 'ruby -e "print ENV[:TEST_ENV_NUMBER.to_s].to_i"'], processes: 4
expect(result.gsub('"', '').chars.sort).to eq(['0', '2', '3', '4'])
end
it "can exec given commands with $TEST_ENV_NUMBER" do
result = run_tests ['-e', 'echo foo-$TEST_ENV_NUMBER'], processes: 4
expect(result.split(/\n+/).sort).to eq(['foo-', 'foo-2', 'foo-3', 'foo-4'])
end
it "can exec given command non-parallel" do
result = run_tests(
['-e', 'ruby -e "sleep(rand(10)/100.0); puts ENV[:TEST_ENV_NUMBER.to_s].inspect"'],
processes: 4,
add: ['--non-parallel']
)
expect(result.split(/\n+/)).to eq(['""', '"2"', '"3"', '"4"'])
end
it "can exec given command with a restricted set of groups" do
result = run_tests(
['-e', 'ruby -e "print ENV[:TEST_ENV_NUMBER.to_s].to_i"'],
process: 4,
add: ['--only-group', '1,3']
)
expect(result.gsub('"', '').chars.sort).to eq(['0', '3'])
end
it "can serialize stdout" do
result = run_tests(
['-e', 'ruby -e "5.times{sleep 0.01;puts ENV[:TEST_ENV_NUMBER.to_s].to_i;STDOUT.flush}"'],
processes: 2,
add: ['--serialize-stdout']
)
expect(result).not_to match(/0.*2.*0/m)
expect(result).not_to match(/2.*0.*2/m)
end
it "runs command in parallel with files as arguments" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
result = run_tests "spec", type: 'rspec', add: ["--exec-args", "echo"]
expect(result).to include_exactly_times('spec/xxx_spec.rb', 1)
expect(result).to include_exactly_times('spec/xxx2_spec.rb', 1)
end
it "runs two commands in parallel with files as arguments" do
write 'spec/xxx_spec.rb', 'p ARGV; describe("it"){it("should"){puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
# need to `--` so sh uses them as arguments that then go into $@
result = run_tests "spec", type: 'rspec', add: ["--exec-args", "sh -c \"echo 'hello world' && rspec $@\" --"]
expect(result).to include_exactly_times('hello world', 2)
expect(result).to include_exactly_times('TEST1', 1)
expect(result).to include_exactly_times('TEST2', 1)
end
it "exists with success if all sub-processes returned success" do
expect(system(*executable, '-e', 'cat /dev/null', '-n', '4')).to eq(true)
end
it "exists with failure if any sub-processes returned failure" do
expect(system(*executable, '-e', 'test -e xxxx', '-n', '4')).to eq(false)
end
end
['rspec', 'cucumber', 'spinach'].each do |type|
it "runs through parallel_#{type}" do
test_version = IO.popen([*executable, '-v'], &:read)
expect($?.success?).to be(true)
type_version = IO.popen(['ruby', "#{bin_folder}/parallel_#{type}", '-v'], &:read)
expect($?.success?).to be(true)
expect(type_version).to eq(test_version)
end
end
it "runs with --group-by found" do
# it only tests that it does not blow up, as it did before fixing...
write "spec/x1_spec.rb", "puts 'TEST111'"
run_tests ["spec"], type: 'rspec', add: ['--group-by', 'found']
end
it "runs in parallel" do
2.times do |i|
write "spec/xxx#{i}_spec.rb", 'STDOUT.sync = true; describe("it") { it("should"){ puts "START"; sleep 1; puts "END" } }'
end
result = run_tests(["spec"], processes: 2, type: 'rspec')
expect(result.scan(/START|END/)).to eq(["START", "START", "END", "END"])
end
it "disables spring so correct database is used" do
write "spec/xxx_spec.rb", 'puts "SPRING: #{ENV["DISABLE_SPRING"]}"'
result = run_tests(["spec"], processes: 2, type: 'rspec')
expect(result).to include "SPRING: 1"
end
it "can enable spring" do
write "spec/xxx_spec.rb", 'puts "SPRING: #{ENV["DISABLE_SPRING"]}"'
result = run_tests(["spec"], processes: 2, type: 'rspec', export: { "DISABLE_SPRING" => "0" })
expect(result).to include "SPRING: 0"
end
it "runs with files that have spaces" do
write "test/xxx _test.rb", 'puts "TEST_SUCCESS"'
result = run_tests(["test"], processes: 2, type: 'test')
expect(result).to include "TEST_SUCCESS"
end
it "uses relative paths for easy copying" do
write "test/xxx_test.rb", 'puts "Test output: YES"'
result = run_tests(["test"], processes: 2, type: 'test', add: ['--verbose'])
expect(result).to include "Test output: YES"
expect(result).to include "\\[test/xxx_test.rb\\]"
expect(result).not_to include Dir.pwd
end
it "can run with given files" do
write "spec/x1_spec.rb", "puts 'TEST111'"
write "spec/x2_spec.rb", "puts 'TEST222'"
write "spec/x3_spec.rb", "puts 'TEST333'"
result = run_tests ["spec/x1_spec.rb", "spec/x3_spec.rb"], type: 'rspec'
expect(result).to include('TEST111')
expect(result).to include('TEST333')
expect(result).not_to include('TEST222')
end
it "can run with test-options" do
write "spec/x1_spec.rb", "111"
write "spec/x2_spec.rb", "111"
result = run_tests ["spec"], add: ["--test-options", "--version"], processes: 2, type: 'rspec'
expect(result).to match(/\d+\.\d+\.\d+.*\d+\.\d+\.\d+/m) # prints version twice
end
it "runs with PARALLEL_TEST_PROCESSORS processes" do
skip if RUBY_PLATFORM == "java" # execution expired issue on JRuby
processes = 5
processes.times do |i|
write "spec/x#{i}_spec.rb", "puts %{ENV-\#{ENV['TEST_ENV_NUMBER']}-}"
end
result = run_tests(
["spec"], export: { "PARALLEL_TEST_PROCESSORS" => processes.to_s }, processes: processes, type: 'rspec'
)
expect(result.scan(/ENV-.?-/)).to match_array(["ENV--", "ENV-2-", "ENV-3-", "ENV-4-", "ENV-5-"])
end
it "filters test by given pattern and relative paths" do
write "spec/x_spec.rb", "puts 'TESTXXX'"
write "spec/y_spec.rb", "puts 'TESTYYY'"
write "spec/z_spec.rb", "puts 'TESTZZZ'"
result = run_tests ["spec"], add: ["-p", "^spec/(x|z)"], type: "rspec"
expect(result).to include('TESTXXX')
expect(result).not_to include('TESTYYY')
expect(result).to include('TESTZZZ')
end
it "excludes test by given pattern and relative paths" do
write "spec/x_spec.rb", "puts 'TESTXXX'"
write "spec/acceptance/y_spec.rb", "puts 'TESTYYY'"
write "spec/integration/z_spec.rb", "puts 'TESTZZZ'"
result = run_tests ["spec"], add: ["--exclude-pattern", "spec/(integration|acceptance)"], type: "rspec"
expect(result).to include('TESTXXX')
expect(result).not_to include('TESTYYY')
expect(result).not_to include('TESTZZZ')
end
it "can wait_for_other_processes_to_finish" do
skip if RUBY_PLATFORM == "java" # just too slow ...
write "test/a_test.rb", "require 'parallel_tests'; sleep 0.5 ; ParallelTests.wait_for_other_processes_to_finish; puts 'OutputA'"
write "test/b_test.rb", "sleep 1; puts 'OutputB'"
write "test/c_test.rb", "sleep 1.5; puts 'OutputC'"
write "test/d_test.rb", "sleep 2; puts 'OutputD'"
actual = run_tests(["test"], processes: 4).scan(/Output[ABCD]/)
actual_sorted = [*actual[0..2].sort, actual[3]]
expect(actual_sorted).to match(["OutputB", "OutputC", "OutputD", "OutputA"])
end
it "can run only a single group" do
skip if RUBY_PLATFORM == "java" # just too slow ...
write "test/long_test.rb", "puts 'this is a long test'"
write "test/short_test.rb", "puts 'short test'"
group_1_result = run_tests(["test"], processes: 2, add: ['--only-group', '1'])
expect(group_1_result).to include("this is a long test")
expect(group_1_result).not_to include("short test")
group_2_result = run_tests(["test"], processes: 2, add: ['--only-group', '2'])
expect(group_2_result).not_to include("this is a long test")
expect(group_2_result).to include("short test")
end
it "shows nice --help" do
result = run_tests ["--help"]
expect(
result[/(.*)How many processes/, 1].size
).to(
eq(result[/( +)found /, 1].size),
"Multiline option description must align with regular option description"
)
end
it "can run with uncommon file names" do
skip if RUBY_PLATFORM == "java" # just too slow ...
write "test/long ( stuff ) _test.rb", "puts 'hey'"
expect(run_tests(["test"], processes: 2)).to include("hey")
end
context "RSpec" do
it_runs_the_default_folder_if_it_exists "rspec", "spec"
it "captures seed with random failures with --verbose" do
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
result = run_tests ["spec", "--verbose"], add: ["--test-options", "--seed 1234"], fail: true, type: 'rspec'
expect(result).to include("Randomized with seed 1234")
expect(result).to include("bundle exec rspec --seed 1234 spec/xxx2_spec.rb")
end
end
context "Test::Unit" do
it "runs" do
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
result = run_tests ["test"]
expect(result).to include('1 test')
end
it "passes test options" do
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
result = run_tests(["test"], add: ['--test-options', '-v'])
expect(result).to include('test_xxx') # verbose output of every test
end
it_runs_the_default_folder_if_it_exists "test", "test"
end
context "Cucumber" do
before do
write "features/steps/a.rb", "
Given('I print TEST_ENV_NUMBER'){ puts \"YOUR TEST ENV IS \#{ENV['TEST_ENV_NUMBER']}!\" }
And('I sleep a bit'){ sleep 0.5 }
And('I pass'){ true }
And('I fail'){ fail }
"
end
it "runs tests which outputs accented characters" do
write "features/good1.feature", "Feature: xxx\n Scenario: xxx\n Given I print accented characters"
write "features/steps/a.rb", "#encoding: utf-8\nGiven('I print accented characters'){ puts \"I tu też\" }"
result = run_tests ["features"], type: "cucumber", add: ['--pattern', 'good']
expect(result).to include('I tu też')
end
it "passes TEST_ENV_NUMBER when running with pattern (issue #86)" do
write "features/good1.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
write "features/good2.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
write "features/b.feature", "Feature: xxx\n Scenario: xxx\n Given I FAIL"
write "features/steps/a.rb", "Given('I print TEST_ENV_NUMBER'){ puts \"YOUR TEST ENV IS \#{ENV['TEST_ENV_NUMBER']}!\" }"
result = run_tests ["features"], type: "cucumber", add: ['--pattern', 'good']
expect(result).to include('YOUR TEST ENV IS 2!')
expect(result).to include('YOUR TEST ENV IS !')
expect(result).not_to include('I FAIL')
end
it "writes a runtime log" do
skip "TODO find out why this fails" if RUBY_PLATFORM == "java"
log = "tmp/parallel_runtime_cucumber.log"
write(log, "x")
2.times do |i|
# needs sleep so that runtime loggers dont overwrite each other initially
write "features/good#{i}.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER\n And I sleep a bit"
end
run_tests ["features"], type: "cucumber"
expect(read(log).gsub(/\.\d+/, '').split("\n")).to match_array(["features/good0.feature:0", "features/good1.feature:0"])
end
it "runs each feature once when there are more processes then features (issue #89)" do
2.times do |i|
write "features/good#{i}.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
end
result = run_tests ["features"], type: "cucumber", add: ['-n', '3']
expect(result.scan(/YOUR TEST ENV IS \d?!/).sort).to eq(["YOUR TEST ENV IS !", "YOUR TEST ENV IS 2!"])
end
it_runs_the_default_folder_if_it_exists "cucumber", "features"
it "collates failing scenarios" do
write "features/pass.feature", "Feature: xxx\n Scenario: xxx\n Given I pass"
write "features/fail1.feature", "Feature: xxx\n Scenario: xxx\n Given I fail"
write "features/fail2.feature", "Feature: xxx\n Scenario: xxx\n Given I fail"
results = run_tests ["features"], processes: 3, type: "cucumber", fail: true
failing_scenarios = if Gem.win_platform? && RUBY_VERSION < "3.3.0"
["cucumber features/fail1.feature:2 # Scenario: xxx", "cucumber features/fail2.feature:2 # Scenario: xxx"]
else
["cucumber features/fail2.feature:2 # Scenario: xxx", "cucumber features/fail1.feature:2 # Scenario: xxx"]
end
results.gsub!(/.*WARNING.*\n/, "")
expect(results).to include <<-EOF.gsub(' ', '')
Failing Scenarios:
#{failing_scenarios[0]}
#{failing_scenarios[1]}
3 scenarios (2 failed, 1 passed)
3 steps (2 failed, 1 passed)
EOF
end
it "groups by scenario" do
write "features/long.feature", <<-EOS
Feature: xxx
Scenario: xxx
Given I print TEST_ENV_NUMBER
Scenario: xxx
Given I print TEST_ENV_NUMBER
Scenario Outline: xxx
Given I print TEST_ENV_NUMBER
Examples:
| num |
| one |
| two |
EOS
result = run_tests ["features"], type: "cucumber", add: ["--group-by", "scenarios"]
expect(result).to include("2 processes for 4 scenarios")
end
it "groups by step" do
write "features/good1.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
write "features/good2.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
result = run_tests ["features"], type: "cucumber", add: ['--group-by', 'steps']
expect(result).to include("2 processes for 2 features")
end
it "captures seed with random failures with --verbose" do
write "features/good1.feature", "Feature: xxx\n Scenario: xxx\n Given I fail"
result = run_tests ["features", "--verbose"], type: "cucumber", add: ["--test-options", "--order random:1234"], fail: true
expect(result).to include("Randomized with seed 1234")
expect(result).to match(%r{bundle exec cucumber "?features/good1.feature"? --order random:1234})
end
end
context "Spinach" do
before do
write "features/steps/a.rb", <<-RUBY.strip_heredoc
class A < Spinach::FeatureSteps
Given 'I print TEST_ENV_NUMBER' do
puts "YOUR TEST ENV IS \#{ENV['TEST_ENV_NUMBER']}!"
end
And 'I sleep a bit' do
sleep 0.2
end
end
RUBY
end
it "runs tests which outputs accented characters" do
write "features/good1.feature", "Feature: a\n Scenario: xxx\n Given I print accented characters"
write "features/steps/a.rb", "#encoding: utf-8\nclass A < Spinach::FeatureSteps\nGiven 'I print accented characters' do\n puts \"I tu też\" \n end\nend"
result = run_tests ["features"], type: "spinach", add: ['features/good1.feature'] # , :add => '--pattern good'
expect(result).to include('I tu też')
end
it "passes TEST_ENV_NUMBER when running with pattern (issue #86)" do
write "features/good1.feature", "Feature: a\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
write "features/good2.feature", "Feature: a\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
write "features/b.feature", "Feature: b\n Scenario: xxx\n Given I FAIL" # Expect this not to be run
result = run_tests ["features"], type: "spinach", add: ['--pattern', 'good']
expect(result).to include('YOUR TEST ENV IS 2!')
expect(result).to include('YOUR TEST ENV IS !')
expect(result).not_to include('I FAIL')
end
it "writes a runtime log" do
skip 'not yet implemented -- custom runtime logging'
log = "tmp/parallel_runtime_spinach.log"
write(log, "x")
2.times do |i|
# needs sleep so that runtime loggers dont overwrite each other initially
write "features/good#{i}.feature", "Feature: A\n Scenario: xxx\n Given I print TEST_ENV_NUMBER\n And I sleep a bit"
end
run_tests ["features"], type: "spinach"
expect(read(log).gsub(/\.\d+/, '').split("\n")).to match_array(["features/good0.feature:0", "features/good1.feature:0"])
end
it "runs each feature once when there are more processes then features (issue #89)" do
2.times do |i|
write "features/good#{i}.feature", "Feature: A\n Scenario: xxx\n Given I print TEST_ENV_NUMBER\n"
end
result = run_tests ["features"], type: "spinach", add: ['-n', '3']
expect(result.scan(/YOUR TEST ENV IS \d?!/).sort).to eq(["YOUR TEST ENV IS !", "YOUR TEST ENV IS 2!"])
end
it_runs_the_default_folder_if_it_exists "spinach", "features"
end
describe "graceful shutdown" do
# Process.kill on Windows doesn't work as expected. It kills all process group instead of just one process.
it "passes on int signal to child processes", unless: Gem.win_platform? do
timeout = 2
write(
"spec/test_spec.rb",
"sleep #{timeout}; describe { specify { p 'Should not get here' }; specify { p 'Should not get here either'} }"
)
pid = nil
Thread.new { sleep timeout - 0.5; Process.kill("INT", pid) }
result = run_tests(["spec"], processes: 2, type: 'rspec', fail: true) { |io| pid = io.pid }
expect(result).to include("RSpec is shutting down")
expect(result).to_not include("Should not get here")
expect(result).to_not include("Should not get here either")
end
# Process.kill on Windows doesn't work as expected. It kills all process group instead of just one process.
it "exits immediately if another int signal is received", unless: Gem.win_platform? do
timeout = 2
write "spec/test_spec.rb", "describe { specify { sleep #{timeout}; p 'Should not get here'} }"
pid = nil
Thread.new { sleep timeout - 0.5; Process.kill("INT", pid) }
Thread.new { sleep timeout - 0.3; Process.kill("INT", pid) }
result = run_tests(["spec"], processes: 2, type: 'rspec', fail: false) { |io| pid = io.pid }
expect(result).to_not include("Should not get here")
end
end
describe "--test-file-limit" do
let(:test_count) { 3 }
before do
test_count.times do |i|
write "spec/x#{i}_spec.rb", "puts %(TEST-\#{ENV['TEST_ENV_NUMBER']}-\#{Process.pid})"
end
end
it "runs in batches" do
result = run_tests ["spec"], type: 'rspec', add: ['--test-file-limit', '1', '--first-is-1', '-n', '2']
expect(result.scan(/TEST-\d/).sort).to eq(["TEST-1", "TEST-1", "TEST-2"])
pids = result.scan(/TEST-\d-(\d+)/).flatten.uniq
expect(pids.size).to eq test_count # did not run 2 tests in the same process
end
it "does not run in batches when above limit" do
result = run_tests ["spec"], type: 'rspec', add: ['--test-file-limit', '2', '--first-is-1', '-n', '2']
expect(result.scan(/TEST-\d/).sort).to eq(["TEST-1", "TEST-1", "TEST-2"])
pids = result.scan(/TEST-\d-(\d+)/).flatten.uniq
expect(pids.size).to eq 2
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/ 0000775 0000000 0000000 00000000000 15043316274 0020513 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/parallel_tests/cli_spec.rb 0000664 0000000 0000000 00000042576 15043316274 0022637 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
require "parallel_tests/cli"
require "parallel_tests/rspec/runner"
describe ParallelTests::CLI do
subject { ParallelTests::CLI.new }
describe "#parse_options" do
let(:defaults) { { files: ["test"] } }
def call(*args)
subject.send(:parse_options!, *args)
end
it "does not fail without file" do
expect(subject).not_to receive(:abort)
call(["-n3", "-t", "rspec"])
end
it "cleanups file paths" do
expect(call(["./test"])).to eq(defaults)
end
it "parses execute" do
expect(call(["--exec", "echo"])).to eq(execute: ["echo"])
end
it "parses execute arguments" do
expect(call(["test", "--exec-args", "echo"])).to eq(defaults.merge(execute_args: ["echo"]))
end
it "parses excludes pattern" do
expect(call(["test", "--exclude-pattern", "spec/"])).to eq(defaults.merge(exclude_pattern: /spec\//))
end
it "parses regular count" do
expect(call(["test", "-n3"])).to eq(defaults.merge(count: 3))
end
it "parses count 0 as non-parallel" do
expect(call(["test", "-n0"])).to eq(defaults.merge(non_parallel: true))
end
it "parses non-parallel as non-parallel" do
expect(call(["test", "--non-parallel"])).to eq(defaults.merge(non_parallel: true))
end
it "finds the correct type when multiple are given" do
call(["test", "--type", "test", "-t", "rspec"])
expect(subject.instance_variable_get(:@runner)).to eq(ParallelTests::RSpec::Runner)
end
it "parses nice as nice" do
expect(call(["test", "--nice"])).to eq(defaults.merge(nice: true))
end
it "parses --verbose" do
expect(call(["test", "--verbose"])).to eq(defaults.merge(verbose: true))
end
it "parses --verbose-command" do
expect(call(['test', '--verbose-command'])).to eq(
defaults.merge(verbose_process_command: true, verbose_rerun_command: true)
)
end
it "parses --verbose-process-command" do
expect(call(['test', '--verbose-process-command'])).to eq(
defaults.merge(verbose_process_command: true)
)
end
it "parses --verbose-rerun-command" do
expect(call(['test', '--verbose-rerun-command'])).to eq(
defaults.merge(verbose_rerun_command: true)
)
end
it "parses --failure-exit-code" do
expect(call(["test", "--failure-exit-code", "42"])).to eq(defaults.merge(failure_exit_code: 42))
end
it "parses --quiet" do
expect(call(["test", "--quiet"])).to eq(defaults.merge(quiet: true))
end
it "fails if both --verbose and --quiet are present" do
expect { call(["test", "--verbose", "--quiet"]) }.to raise_error(RuntimeError)
end
it "parses --suffix" do
expect(call(["test", "--suffix", "_(test|spec).rb$"])).to eq(defaults.merge(suffix: /_(test|spec).rb$/))
end
it "parses --first-is-1" do
expect(call(["test", "--first-is-1"]))
.to eq(defaults.merge(first_is_1: true))
end
it "parses allow-duplicates" do
expect(call(["test", "--allow-duplicates"])).to eq(defaults.merge(allow_duplicates: true))
end
context "parse only-group" do
it "group_by should be set to filesize" do
expect(call(["test", "--only-group", '1'])).to eq(defaults.merge(only_group: [1], group_by: :filesize))
end
it "allows runtime" do
expect(call(["test", "--only-group", '1', '--group-by', 'runtime'])).to eq(defaults.merge(only_group: [1], group_by: :runtime))
end
it "raise error when group_by isn't filesize" do
expect do
call(["test", "--only-group", '1', '--group-by', 'steps'])
end.to raise_error(RuntimeError)
end
it "with multiple groups" do
expect(call(["test", "--only-group", '4,5'])).to eq(defaults.merge(only_group: [4, 5], group_by: :filesize))
end
it "with a single group" do
expect(call(["test", "--only-group", '4'])).to eq(defaults.merge(only_group: [4], group_by: :filesize))
end
end
context "single and isolate" do
it "single_process should be an array of patterns" do
expect(call(["test", "--single", '1'])).to eq(defaults.merge(single_process: [/1/]))
end
it "single_process should be an array of patterns" do
expect(call(["test", "--single", '1', "--single", '2'])).to eq(defaults.merge(single_process: [/1/, /2/]))
end
it "isolate should set isolate_count defaults" do
expect(call(["test", "--single", '1', "--isolate"])).to eq(defaults.merge(single_process: [/1/], isolate: true))
end
it "isolate_n should set isolate_count and turn on isolate" do
expect(call(["test", "-n", "3", "--single", '1', "--isolate-n", "2"])).to eq(
defaults.merge(count: 3, single_process: [/1/], isolate_count: 2)
)
end
end
context "specify groups" do
it "groups can be just one string" do
expect(call(["test", "--specify-groups", 'test'])).to eq(defaults.merge(specify_groups: 'test'))
end
it "groups can be a string separated by commas and pipes" do
expect(call(["test", "--specify-groups", 'test1,test2|test3'])).to eq(defaults.merge(specify_groups: 'test1,test2|test3'))
end
end
context "when the -- option separator is used" do
it "interprets arguments as files/directories" do
expect(call(['--', 'test'])).to eq(files: ['test'])
expect(call(['--', './test'])).to eq(files: ['test'])
expect(call(['--', 'test', 'test2'])).to eq(files: ['test', 'test2'])
expect(call(['--', '--foo', 'test'])).to eq(files: ['--foo', 'test'])
expect(call(['--', 'test', '--foo', 'test2'])).to eq(files: ['test', '--foo', 'test2'])
end
it "correctly handles arguments with spaces" do
expect(call(['--', 'file name with space'])).to eq(files: ['file name with space'])
end
context "when the -o options has also been given" do
it "merges the options together" do
expect(call(['-o', "'-f'", '--', 'test', '--foo', 'test2'])).to eq(files: ['test', '--foo', 'test2'], test_options: ['-f'])
end
end
context "when a second -- option separator is used" do
it "interprets the first set as test_options" do
expect(call(['--', '-r', 'foo', '--', 'test'])).to eq(files: ['test'], test_options: ['-r', 'foo'])
expect(call(['--', '-r', 'foo', '--', 'test', 'test2'])).to eq(files: ['test', 'test2'], test_options: ['-r', 'foo'])
expect(call(['--', '-r', 'foo', '-o', 'out.log', '--', 'test', 'test2'])).to eq(files: ['test', 'test2'], test_options: ['-r', 'foo', '-o', 'out.log'])
end
context "when existing test_options have previously been given" do
it "appends the new options" do
expect(call(['-o', '-f', '--', '-r', 'foo.rb', '--', 'test'])).to eq(files: ['test'], test_options: ['-f', '-r', 'foo.rb'])
end
it "correctly handles argument values with spaces" do
argv = ["-o 'path with spaces1'", '--', '--out', 'path with spaces2', '--', 'foo']
expected_test_options = ['path with spaces1', '--out', 'path with spaces2']
expect(call(argv)).to eq(files: ['foo'], test_options: expected_test_options)
end
end
end
end
end
describe "#load_runner" do
it "requires and loads default runner" do
expect(subject).to receive(:require).with("parallel_tests/test/runner")
expect(subject.send(:load_runner, "test")).to eq(ParallelTests::Test::Runner)
end
it "requires and loads rspec runner" do
expect(subject).to receive(:require).with("parallel_tests/rspec/runner")
expect(subject.send(:load_runner, "rspec")).to eq(ParallelTests::RSpec::Runner)
end
it "requires and loads runner with underscores" do
expect(subject).to receive(:require).with("parallel_tests/my_test_runner/runner")
expect(subject.send(:load_runner, "my_test_runner")).to eq(ParallelTests::MyTestRunner::Runner)
end
it "fails to load unfindable runner" do
expect do
expect(subject.send(:load_runner, "foo")).to eq(ParallelTests::RSpec::Runner)
end.to raise_error(LoadError)
end
end
describe ".report_failure_rerun_command" do
let(:single_failed_command) { [{ exit_status: 1, command: ['foo'], seed: nil, output: 'blah' }] }
it "prints nothing if there are no failures" do
expect($stdout).not_to receive(:puts)
subject.send(
:report_failure_rerun_commmand,
[
{ exit_status: 0, command: 'foo', seed: nil, output: 'blah' }
],
{ verbose: true }
)
end
def self.it_prints_nothing_about_rerun_commands(options)
it 'prints nothing about rerun commands' do
expect do
subject.send(:report_failure_rerun_commmand, single_failed_command, options)
end.to_not output(/Use the following command to run the group again/).to_stdout
end
end
describe "failure" do
before do
subject.instance_variable_set(:@runner, ParallelTests::Test::Runner)
end
context 'without options' do
it_prints_nothing_about_rerun_commands({})
end
context 'with verbose disabled' do
it_prints_nothing_about_rerun_commands(verbose: false)
end
context "with verbose rerun command" do
it "prints command if there is a failure" do
expect do
subject.send(:report_failure_rerun_commmand, single_failed_command, verbose_rerun_command: true)
end.to output("\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\nTEST_ENV_NUMBER= PARALLEL_TEST_GROUPS= foo\n").to_stdout
end
end
context 'with verbose' do
it "prints a message and the command if there is a failure" do
expect do
subject.send(:report_failure_rerun_commmand, single_failed_command, verbose: true)
end.to output("\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\nTEST_ENV_NUMBER= PARALLEL_TEST_GROUPS= foo\n").to_stdout
end
it "prints multiple commands if there are multiple failures" do
expect do
subject.send(
:report_failure_rerun_commmand,
[
{ exit_status: 1, command: ['foo'], seed: nil, output: 'blah' },
{ exit_status: 1, command: ['bar'], seed: nil, output: 'blah' },
{ exit_status: 1, command: ['baz'], seed: nil, output: 'blah' }
],
{ verbose: true }
)
end.to output(/\sfoo\n.+?\sbar\n.+?\sbaz/).to_stdout
end
it "only includes failures" do
expect do
subject.send(
:report_failure_rerun_commmand,
[
{ exit_status: 1, command: ['foo', '--color'], seed: nil, output: 'blah' },
{ exit_status: 0, command: ['bar'], seed: nil, output: 'blah' },
{ exit_status: 1, command: ['baz'], seed: nil, output: 'blah' }
],
{ verbose: true }
)
end.to output(/\sfoo --color\n.+?\sbaz/).to_stdout
end
it "prints the command with the seed added by the runner" do
command = ['rspec', '--color', 'spec/foo_spec.rb']
seed = 555
expect(ParallelTests::Test::Runner).to receive(:command_with_seed).with(command, seed)
.and_return(['my', 'seeded', 'command', 'result', '--seed', seed])
single_failed_command[0].merge!(seed: seed, command: command)
expect do
subject.send(:report_failure_rerun_commmand, single_failed_command, verbose: true)
end.to output(/my seeded command result --seed 555/).to_stdout
end
end
end
end
describe "#final_fail_message" do
before do
subject.instance_variable_set(:@runner, ParallelTests::Test::Runner)
end
it 'returns a plain fail message if colors are nor supported' do
expect(subject).to receive(:use_colors?).and_return(false)
expect(subject.send(:final_fail_message)).to eq("Tests Failed")
end
it 'returns a colorized fail message if colors are supported' do
expect(subject).to receive(:use_colors?).and_return(true)
expect(subject.send(:final_fail_message)).to eq("\e[31mTests Failed\e[0m")
end
end
describe "#run_tests_in_parallel" do
context "specific groups to run" do
let(:results) { { stdout: "", exit_status: 0 } }
let(:common_options) do
{ files: ["test"], group_by: :filesize, first_is_1: false }
end
before do
allow(subject).to receive(:puts)
expect(subject).to receive(:load_runner).with("my_test_runner").and_return(ParallelTests::MyTestRunner::Runner)
allow(ParallelTests::MyTestRunner::Runner).to receive(:test_file_name).and_return("test")
expect(ParallelTests::MyTestRunner::Runner).to receive(:tests_in_groups).and_return(
[
['aaa', 'bbb'],
['ccc', 'ddd'],
['eee', 'fff']
]
)
expect(subject).to receive(:report_results).and_return(nil)
end
it "calls run_tests once when one group specified" do
expect(subject).to receive(:run_tests).once.and_return(results)
subject.run(['test', '-n', '3', '--only-group', '1', '-t', 'my_test_runner'])
end
it "calls run_tests twice when two groups are specified" do
expect(subject).to receive(:run_tests).twice.and_return(results)
subject.run(['test', '-n', '3', '--only-group', '1,2', '-t', 'my_test_runner'])
end
it "run only one group specified" do
options = common_options.merge(count: 3, only_group: [2])
expect(subject).to receive(:run_tests).once.with(['ccc', 'ddd'], 0, 1, options).and_return(results)
subject.run(['test', '-n', '3', '--only-group', '2', '-t', 'my_test_runner'])
end
it "run last group when passing a group that is not filled" do
count = 3
options = common_options.merge(count: count, only_group: [count])
expect(subject).to receive(:run_tests).once.with(['eee', 'fff'], 0, 1, options).and_return(results)
subject.run(['test', '-n', count.to_s, '--only-group', count.to_s, '-t', 'my_test_runner'])
end
it "run twice with multiple groups" do
skip "fails on jruby" if RUBY_PLATFORM == "java"
options = common_options.merge(count: 3, only_group: [2, 3])
expect(subject).to receive(:run_tests).once.with(['ccc', 'ddd'], 0, 1, options).and_return(results)
expect(subject).to receive(:run_tests).once.with(['eee', 'fff'], 1, 1, options).and_return(results)
subject.run(['test', '-n', '3', '--only-group', '2,3', '-t', 'my_test_runner'])
end
end
context 'when --allow-duplicates' do
let(:results) { { stdout: "", exit_status: 0 } }
let(:processes) { 2 }
let(:common_options) do
{ files: ['test'], allow_duplicates: true, first_is_1: false }
end
before do
allow(subject).to receive(:puts)
expect(subject).to receive(:load_runner).with("my_test_runner").and_return(ParallelTests::MyTestRunner::Runner)
allow(ParallelTests::MyTestRunner::Runner).to receive(:test_file_name).and_return("test")
expect(subject).to receive(:report_results).and_return(nil)
end
before do
expect(ParallelTests::MyTestRunner::Runner).to receive(:tests_in_groups).and_return(
[
['foo'],
['foo'],
['bar']
]
)
end
it "calls run_tests with --only-group" do
options = common_options.merge(count: processes, only_group: [2, 3], group_by: :filesize)
expect(subject).to receive(:run_tests).once.with(['foo'], 0, 1, options).and_return(results)
expect(subject).to receive(:run_tests).once.with(['bar'], 1, 1, options).and_return(results)
subject.run(['test', '-n', processes.to_s, '--allow-duplicates', '--only-group', '2,3', '-t', 'my_test_runner'])
end
it "calls run_tests with --first-is-1" do
options = common_options.merge(count: processes, first_is_1: true)
expect(subject).to receive(:run_tests).once.with(['foo'], 0, processes, options).and_return(results)
expect(subject).to receive(:run_tests).once.with(['foo'], 1, processes, options).and_return(results)
expect(subject).to receive(:run_tests).once.with(['bar'], 2, processes, options).and_return(results)
subject.run(['test', '-n', processes.to_s, '--first-is-1', '--allow-duplicates', '-t', 'my_test_runner'])
end
end
end
describe "#display_duration" do
def call(*args)
subject.send(:detailed_duration, *args)
end
it "displays for durations near one minute" do
expect(call(59)).to eq(nil)
expect(call(60)).to eq(" (1:00)")
expect(call(61)).to eq(" (1:01)")
end
it "displays for durations near one hour" do
expect(call(3599)).to eq(" (59:59)")
expect(call(3600)).to eq(" (1:00:00)")
expect(call(3601)).to eq(" (1:00:01)")
end
it "displays the correct string for miscellaneous durations" do
expect(call(9296)).to eq(" (2:34:56)")
expect(call(45296)).to eq(" (12:34:56)")
expect(call(2756601)).to eq(" (765:43:21)") # hours into three digits? Buy more CI hardware...
expect(call(0)).to eq(nil)
end
end
end
module ParallelTests
module MyTestRunner
class Runner
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/cucumber/ 0000775 0000000 0000000 00000000000 15043316274 0022320 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/parallel_tests/cucumber/failure_logger_spec.rb 0000664 0000000 0000000 00000002307 15043316274 0026647 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
require 'parallel_tests/gherkin/io'
require 'parallel_tests/cucumber/failures_logger'
require 'cucumber/configuration'
describe ParallelTests::Cucumber::FailuresLogger do
let(:parallel_cucumber_failures) { StringIO.new }
let(:config) { Cucumber::Configuration.new(out_stream: parallel_cucumber_failures) }
let(:logger1) { ParallelTests::Cucumber::FailuresLogger.new(config) }
let(:logger2) { ParallelTests::Cucumber::FailuresLogger.new(config) }
let(:logger3) { ParallelTests::Cucumber::FailuresLogger.new(config) }
it "should produce a list of failing scenarios" do
feature1 = double('feature', file: "feature/one.feature")
feature2 = double('feature', file: "feature/two.feature")
logger1.instance_variable_set("@failures", { feature1.file => [1, 3] })
logger2.instance_variable_set("@failures", { feature2.file => [2, 4] })
logger3.instance_variable_set("@failures", {})
config.event_bus.broadcast(Cucumber::Events::TestRunFinished.new)
parallel_cucumber_failures.rewind
expect(parallel_cucumber_failures.read).to eq 'feature/one.feature:1 feature/one.feature:3 feature/two.feature:2 feature/two.feature:4 '
end
end
parallel_tests-5.4.0/spec/parallel_tests/cucumber/runner_spec.rb 0000664 0000000 0000000 00000005203 15043316274 0025170 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
require "parallel_tests/gherkin/runner_behaviour"
require "parallel_tests/cucumber/runner"
describe ParallelTests::Cucumber::Runner do
test_tests_in_groups(ParallelTests::Cucumber::Runner, ".feature")
it_should_behave_like 'gherkin runners' do
let(:runner_name) { 'cucumber' }
let(:runner_class) { ParallelTests::Cucumber::Runner }
describe :summarize_results do
def call(*args)
runner_class.summarize_results(*args)
end
it "collates failing scenarios" do
results = [
"Failing Scenarios:", "cucumber features/failure:1", "cucumber features/failure:2",
"Failing Scenarios:", "cucumber features/failure:3", "cucumber features/failure:4",
"Failing Scenarios:", "cucumber features/failure:5", "cucumber features/failure:6"
]
output = call(results)
output.gsub!(/.*WARNING.*\n/, "")
expect(output).to eq(<<~TXT)
Failing Scenarios:
cucumber features/failure:1
cucumber features/failure:2
cucumber features/failure:3
cucumber features/failure:4
cucumber features/failure:5
cucumber features/failure:6
TXT
end
it "collates flaky scenarios separately" do
results = [
"Failing Scenarios:", "cucumber features/failure:1", "cucumber features/failure:2",
"Flaky Scenarios:", "cucumber features/failure:3", "cucumber features/failure:4",
"Failing Scenarios:", "cucumber features/failure:5", "cucumber features/failure:6",
"Flaky Scenarios:", "cucumber features/failure:7", "cucumber features/failure:8"
]
expect(call(results)).to eq("Failing Scenarios:\ncucumber features/failure:1\ncucumber features/failure:2\ncucumber features/failure:5\ncucumber features/failure:6\n\nFlaky Scenarios:\ncucumber features/failure:3\ncucumber features/failure:4\ncucumber features/failure:7\ncucumber features/failure:8\n\n")
end
end
end
describe ".command_with_seed" do
def call(*args)
ParallelTests::Cucumber::Runner.command_with_seed(['cucumber', *args], 555)
end
it "adds the randomized seed" do
expect(call).to eq(["cucumber", "--order", "random:555"])
end
it "does not duplicate existing random command" do
expect(call("--order", "random", "good1.feature")).to eq(["cucumber", "good1.feature", "--order", "random:555"])
end
it "does not duplicate existing random command with seed" do
expect(call("--order", "random:123", "good1.feature")).to eq(["cucumber", "good1.feature", "--order", "random:555"])
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/cucumber/scenarios_spec.rb 0000664 0000000 0000000 00000021472 15043316274 0025653 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'tempfile'
require 'parallel_tests/cucumber/scenarios'
describe ParallelTests::Cucumber::Scenarios do
let(:feature_file) do
Tempfile.new('grouper.feature').tap do |feature|
feature.write <<-EOS
Feature: Grouping by scenario
Scenario: First
Given I do nothing
Scenario: Second
Given I don't do anything
Scenario Outline: Third
Given I don't do anything
Examples:
| param |
| value 1 |
| value 2 |
EOS
feature.rewind
end
end
context 'by default' do
it 'returns all the scenarios' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path])
expect(scenarios).to eq [
"#{feature_file.path}:3",
"#{feature_file.path}:6",
"#{feature_file.path}:13",
"#{feature_file.path}:14"
]
end
end
context 'with line numbers' do
it 'only returns scenarios that match the provided lines' do
scenarios = ParallelTests::Cucumber::Scenarios.all(["#{feature_file.path}:6:14"])
expect(scenarios).to eq ["#{feature_file.path}:6", "#{feature_file.path}:14"]
end
end
context 'with tags' do
let(:feature_file) do
Tempfile.new('grouper.feature').tap do |feature|
feature.write <<-EOS
@colours
Feature: Grouping by scenario
@black
Scenario: Black
Given I am black
@white
Scenario: White
Given I am blue
@black @white
Scenario: Gray
Given I am Gray
@red
Scenario Outline: Red
Give I am
@blue
Examples:
| colour |
| magenta |
| fuchsia |
@green
Examples:
| colour |
| yellow |
@blue @green
Examples:
| colour |
| white |
EOS
feature.rewind
end
end
it 'Single Feature Tag: colours' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@colours"])
expect(scenarios.length).to eq 7
end
it 'Single Scenario Tag: white' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@white"])
expect(scenarios.length).to eq 2
end
it 'Multiple Scenario Tags 1: black && white' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@black and @white"])
expect(scenarios.length).to eq 1
end
it 'Multiple Scenario Tags 2: black || white scenarios' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@black or @white"])
expect(scenarios.length).to eq 3
end
it 'Scenario Outline Tag: red' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@red"])
expect(scenarios.length).to eq 4
end
it 'Example Tag: blue' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@blue"])
expect(scenarios.length).to eq 3
end
it 'Multiple Example Tags 1: blue && green' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@blue and @green"])
expect(scenarios.length).to eq 1
end
it 'Multiple Example Tags 2: blue || green' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@blue or @green"])
expect(scenarios.length).to eq 4
end
it 'Single Negative Feature Tag: !colours' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "not @colours"])
expect(scenarios.length).to eq 0
end
it 'Single Negative Scenario Tag: !black' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "not @black"])
expect(scenarios.length).to eq 5
end
it 'Multiple Negative Scenario Tags And: !(black && white)' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["-t", "not (@black and @white)"]
)
expect(scenarios.length).to eq 6
end
it 'Multiple Negative Scenario Tags Or: !(black || red)' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["-t", "not (@black or @red)"]
)
expect(scenarios.length).to eq 1
end
it 'Negative Scenario Outline Tag: !red' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "not @red"])
expect(scenarios.length).to eq 3
end
it 'Negative Example Tag: !blue' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "not @blue"])
expect(scenarios.length).to eq 4
end
it 'Multiple Negative Example Tags 1: !blue && !green' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["-t", "not @blue and not @green"]
)
expect(scenarios.length).to eq 3
end
it 'Multiple Negative Example Tags 2: !blue || !green) ' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["-t", "not @blue or not @green"]
)
expect(scenarios.length).to eq 6
end
it 'Scenario and Example Mixed Tags: black || green' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], test_options: ["-t", "@black or @green"])
expect(scenarios.length).to eq 4
end
it 'Positive and Negative Mixed Tags: red && !blue' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["-t", "@red and not @blue"]
)
expect(scenarios.length).to eq 1
end
it 'Multiple Positive and Negative Mixed Tags: (white && black) || (red && !blue)' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["--tags", "(not @white and @black) or (@red and not @green)"]
)
expect(scenarios.length).to eq 3
end
it 'Ignore Tag Pattern Feature: colours' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], ignore_tag_pattern: "@colours")
expect(scenarios.length).to eq 0
end
it 'Ignore Tag Pattern Scenario: black' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], ignore_tag_pattern: "@black")
expect(scenarios.length).to eq 5
end
it 'Ignore Tag Pattern Scenario Outline: red' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], ignore_tag_pattern: "@red")
expect(scenarios.length).to eq 3
end
it 'Ignore Tag Pattern Example: green' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], ignore_tag_pattern: "@green")
expect(scenarios.length).to eq 5
end
it 'Ignore Tag Pattern Multiple Tags: black || red' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path], ignore_tag_pattern: "@black or @red")
expect(scenarios.length).to eq 1
end
it 'Scenario Mixed tags: black && !blue with Ignore Tag Pattern Multiple Tags: red || white' do
scenarios = ParallelTests::Cucumber::Scenarios.all(
[feature_file.path],
test_options: ["-t", "@black and not @blue"], ignore_tag_pattern: "@red or @white"
)
expect(scenarios.length).to eq 1
end
end
context 'with Rules' do # cuke_modeler >=3.2
let(:feature_file) do
Tempfile.new('grouper.feature').tap do |feature|
feature.write <<-EOS
Feature: Grouping by scenario
Scenario: First
Given I do nothing
Scenario Outline: Second
Given I don't do anything
Examples:
| param |
| value 1 |
| value 2 |
Rule:
Scenario: Third
Given I don't do anything
Rule:
Scenario Outline: Fourth
Given I don't do anything
Examples:
| param |
| value 1 |
| value 2 |
EOS
feature.rewind
end
end
it 'returns all the scenarios' do
scenarios = ParallelTests::Cucumber::Scenarios.all([feature_file.path])
expect(scenarios).to match_array [
"#{feature_file.path}:3",
"#{feature_file.path}:10",
"#{feature_file.path}:11",
"#{feature_file.path}:14",
"#{feature_file.path}:22",
"#{feature_file.path}:23"
]
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/gherkin/ 0000775 0000000 0000000 00000000000 15043316274 0022142 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/parallel_tests/gherkin/listener_spec.rb 0000664 0000000 0000000 00000006616 15043316274 0025337 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'parallel_tests/gherkin/listener'
describe ParallelTests::Gherkin::Listener do
describe :collect do
before(:each) do
@listener = ParallelTests::Gherkin::Listener.new
@listener.uri("feature_file")
end
it "returns steps count" do
3.times { @listener.step(nil) }
expect(@listener.collect).to eq({ "feature_file" => 3 })
end
it "counts background steps separately" do
@listener.background("background")
5.times { @listener.step(nil) }
expect(@listener.collect).to eq({ "feature_file" => 0 })
@listener.scenario("scenario")
2.times { @listener.step(nil) }
expect(@listener.collect).to eq({ "feature_file" => 2 })
@listener.scenario("scenario")
expect(@listener.collect).to eq({ "feature_file" => 2 })
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 12 })
end
it "counts scenario outlines steps separately" do
@listener.scenario_outline("outline")
5.times { @listener.step(nil) }
@listener.examples(double('examples', rows: Array.new(3)))
expect(@listener.collect).to eq({ "feature_file" => 15 })
@listener.scenario("scenario")
2.times { @listener.step(nil) }
expect(@listener.collect).to eq({ "feature_file" => 17 })
@listener.scenario("scenario")
expect(@listener.collect).to eq({ "feature_file" => 17 })
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 17 })
end
it 'counts scenarios that should not be ignored' do
@listener.ignore_tag_pattern = nil
@listener.scenario(double('scenario', tags: [double('tag', name: '@WIP')]))
@listener.step(nil)
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 1 })
@listener.ignore_tag_pattern = /@something_other_than_WIP/
@listener.scenario(double('scenario', tags: [double('tag', name: '@WIP')]))
@listener.step(nil)
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 2 })
end
it 'does not count scenarios that should be ignored' do
@listener.ignore_tag_pattern = /@WIP/
@listener.scenario(double('scenario', tags: [double('tag', name: '@WIP')]))
@listener.step(nil)
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 0 })
end
it 'counts outlines that should not be ignored' do
@listener.ignore_tag_pattern = nil
@listener.scenario_outline(double('scenario', tags: [double('tag', name: '@WIP')]))
@listener.step(nil)
@listener.examples(double('examples', rows: Array.new(3)))
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 3 })
@listener.ignore_tag_pattern = /@something_other_than_WIP/
@listener.scenario_outline(double('scenario', tags: [double('tag', name: '@WIP')]))
@listener.step(nil)
@listener.examples(double('examples', rows: Array.new(3)))
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 6 })
end
it 'does not count outlines that should be ignored' do
@listener.ignore_tag_pattern = /@WIP/
@listener.scenario_outline(double('scenario', tags: [double('tag', name: '@WIP')]))
@listener.step(nil)
@listener.examples(double('examples', rows: Array.new(3)))
@listener.eof
expect(@listener.collect).to eq({ "feature_file" => 0 })
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/gherkin/runner_behaviour.rb 0000664 0000000 0000000 00000020405 15043316274 0026045 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
require "parallel_tests/gherkin/runner"
shared_examples_for 'gherkin runners' do
describe :run_tests do
before do
allow(ParallelTests).to receive(:bundler_enabled?).and_return false
allow(File).to receive(:file?).with('.bundle/environment.rb').and_return false
allow(File).to receive(:file?).with("script/#{runner_name}").and_return true
end
def call(*args)
runner_class.run_tests(*args)
end
it "allows to override runner executable via PARALLEL_TESTS_EXECUTABLE" do
ENV['PARALLEL_TESTS_EXECUTABLE'] = 'script/custom_rspec'
should_run_with ["script/custom_rspec"]
call(['xxx'], 1, 22, {})
end
it "permits setting env options" do
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |_, _, _, options|
expect(options[:env]["TEST"]).to eq("ME")
end
call(['xxx'], 1, 22, { env: { 'TEST' => 'ME' } })
end
it "runs bundle exec {runner_name} when on bundler 0.9" do
allow(ParallelTests).to receive(:bundler_enabled?).and_return true
should_run_with ["bundle", "exec", runner_name]
call(['xxx'], 1, 22, {})
end
it "runs script/{runner_name} when script/{runner_name} is found" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}")
call(['xxx'], 1, 22, {})
end
it "runs {runner_name} by default" do
allow(File).to receive(:file?).with("script/#{runner_name}").and_return false
should_run_with [runner_name]
call(['xxx'], 1, 22, {})
end
it "uses bin/{runner_name} when present" do
allow(File).to receive(:exist?).with("bin/#{runner_name}").and_return true
should_run_with ParallelTests.with_ruby_binary("bin/#{runner_name}")
call(['xxx'], 1, 22, {})
end
it "uses options passed in" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}"), "-p", "default"
call(['xxx'], 1, 22, test_options: ['-p', 'default'])
end
it "sanitizes dangerous file runner_names" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}"), "xx x"
call(['xx x'], 1, 22, {})
end
context "with parallel profile in config/{runner_name}.yml" do
before do
file_contents = 'parallel: -f progress'
allow(Dir).to receive(:glob).and_return ["config/#{runner_name}.yml"]
allow(File).to receive(:read).with("config/#{runner_name}.yml").and_return file_contents
end
it "uses parallel profile" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}"), "xxx", "foo", "bar", "--profile", "parallel"
call(['xxx'], 1, 22, test_options: ['foo', 'bar'])
end
it "uses given profile via --profile" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}"), "--profile", "foo"
call(['xxx'], 1, 22, test_options: ['--profile', 'foo'])
end
it "uses given profile via -p" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}"), "-p", "foo"
call(['xxx'], 1, 22, test_options: ['-p', 'foo'])
end
end
it "does not use parallel profile if config/{runner_name}.yml does not contain it" do
file_contents = 'blob: -f progress'
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}"), "foo", "bar"
expect(Dir).to receive(:glob).and_return ["config/#{runner_name}.yml"]
expect(File).to receive(:read).with("config/#{runner_name}.yml").and_return file_contents
call(['xxx'], 1, 22, test_options: ['foo', 'bar'])
end
it "does not use the parallel profile if config/{runner_name}.yml does not exist" do
should_run_with ParallelTests.with_ruby_binary("script/#{runner_name}") # TODO: this test looks useless...
expect(Dir).to receive(:glob).and_return []
call(['xxx'], 1, 22, {})
end
end
describe :line_is_result? do
it "should match lines with only one scenario" do
line = "1 scenario (1 failed)"
expect(runner_class.line_is_result?(line)).to be_truthy
end
it "should match lines with multiple scenarios" do
line = "2 scenarios (1 failed, 1 passed)"
expect(runner_class.line_is_result?(line)).to be_truthy
end
it "should match lines with only one step" do
line = "1 step (1 failed)"
expect(runner_class.line_is_result?(line)).to be_truthy
end
it "should match lines with multiple steps" do
line = "5 steps (1 failed, 4 passed)"
expect(runner_class.line_is_result?(line)).to be_truthy
end
it "should not match other lines" do
line = ' And I should have "2" emails # features/step_definitions/user_steps.rb:25'
expect(runner_class.line_is_result?(line)).to be_falsey
end
end
describe :find_results do
it "finds multiple results in test output" do
output = <<~EOF
And I should not see "/en/" # features/step_definitions/webrat_steps.rb:87
7 scenarios (3 failed, 4 passed)
33 steps (3 failed, 2 skipped, 28 passed)
/apps/rs/features/signup.feature:2
Given I am on "/" # features/step_definitions/common_steps.rb:12
When I click "register" # features/step_definitions/common_steps.rb:6
And I should have "2" emails # features/step_definitions/user_steps.rb:25
4 scenarios (4 passed)
40 steps (40 passed)
And I should not see "foo" # features/step_definitions/webrat_steps.rb:87
1 scenario (1 passed)
1 step (1 passed)
EOF
expect(runner_class.find_results(output)).to eq(
[
"7 scenarios (3 failed, 4 passed)",
"33 steps (3 failed, 2 skipped, 28 passed)",
"4 scenarios (4 passed)",
"40 steps (40 passed)",
"1 scenario (1 passed)", "1 step (1 passed)"
]
)
end
end
describe :summarize_results do
def call(*args)
runner_class.summarize_results(*args)
end
it "sums up results for scenarios and steps separately from each other" do
results = [
"7 scenarios (2 failed, 1 flaky, 4 passed)",
"33 steps (3 failed, 2 skipped, 28 passed)",
"4 scenarios (4 passed)", "40 steps (40 passed)",
"1 scenario (1 passed)", "1 step (1 passed)"
]
expect(call(results)).to eq("12 scenarios (2 failed, 1 flaky, 9 passed)\n74 steps (3 failed, 2 skipped, 69 passed)")
end
it "adds same results with plurals" do
results = [
"1 scenario (1 passed)", "2 steps (2 passed)",
"2 scenarios (2 passed)", "7 steps (7 passed)"
]
expect(call(results)).to eq("3 scenarios (3 passed)\n9 steps (9 passed)")
end
it "adds non-similar results" do
results = [
"1 scenario (1 passed)", "1 step (1 passed)",
"2 scenarios (1 failed, 1 pending)", "2 steps (1 failed, 1 pending)"
]
expect(call(results)).to eq("3 scenarios (1 failed, 1 pending, 1 passed)\n3 steps (1 failed, 1 pending, 1 passed)")
end
it "does not pluralize 1" do
expect(call(["1 scenario (1 passed)", "1 step (1 passed)"])).to eq("1 scenario (1 passed)\n1 step (1 passed)")
end
end
describe 'grouping by scenarios for cucumber' do
def call(*args)
runner_class.send(:run_tests, *args)
end
it 'groups cucumber invocation by feature files to achieve correct cucumber hook behaviour' do
test_files = ['features/a.rb:23', 'features/a.rb:44', 'features/b.rb:12']
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |a, _b, _c, _d|
argv = a.last(2)
expect(argv).to eq(["features/a.rb:23:44", "features/b.rb:12"])
end
call(test_files, 1, 2, { group_by: :scenarios })
end
end
describe ".find_tests" do
def call(*args)
ParallelTests::Gherkin::Runner.send(:find_tests, *args)
end
it "doesn't find backup files with the same name as test files" do
with_files(['a/x.feature', 'a/x.feature.bak']) do |root|
expect(call(["#{root}/"])).to eq(
[
"#{root}/a/x.feature"
]
)
end
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/grouper_spec.rb 0000664 0000000 0000000 00000010656 15043316274 0023545 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
require 'parallel_tests/grouper'
require 'parallel_tests/cucumber/scenarios'
require 'tmpdir'
describe ParallelTests::Grouper do
describe '.by_steps' do
def write(file, content)
File.write(file, content)
end
it "sorts features by steps" do
tmpdir = nil
result = Dir.mktmpdir do |dir|
tmpdir = dir
write("#{dir}/a.feature", "Feature: xxx\n Scenario: xxx\n Given something")
write(
"#{dir}/b.feature",
"Feature: xxx\n Scenario: xxx\n Given something\n Scenario: yyy\n Given something"
)
write("#{dir}/c.feature", "Feature: xxx\n Scenario: xxx\n Given something")
ParallelTests::Grouper.by_steps(["#{dir}/a.feature", "#{dir}/b.feature", "#{dir}/c.feature"], 2, {})
end
# testing inside mktmpdir is always green
expect(result).to match_array(
[
["#{tmpdir}/a.feature", "#{tmpdir}/c.feature"],
["#{tmpdir}/b.feature"]
]
)
end
end
describe '.in_even_groups_by_size' do
let(:files_with_size) { { "1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5 } }
def call(num_groups, options = {})
ParallelTests::Grouper.in_even_groups_by_size(files_with_size, num_groups, options)
end
it "groups 1 by 1 for same groups as size" do
expect(call(5)).to eq([["5"], ["4"], ["3"], ["2"], ["1"]])
end
it "groups into even groups" do
expect(call(2)).to eq([["1", "2", "5"], ["3", "4"]])
end
it "groups into a single group" do
expect(call(1)).to eq([["1", "2", "3", "4", "5"]])
end
it "adds empty groups if there are more groups than feature files" do
expect(call(6)).to eq([["5"], ["4"], ["3"], ["2"], ["1"], []])
end
it "groups single items into first group" do
expect(call(2, single_process: [/1|2|3|4/])).to eq([["1", "2", "3", "4"], ["5"]])
end
it "groups single items into specified isolation groups" do
expect(call(3, single_process: [/1|2|3|4/], isolate_count: 2)).to eq([["1", "4"], ["2", "3"], ["5"]])
end
it "groups single items with others if there are too few" do
expect(call(2, single_process: [/1/])).to eq([["1", "3", "4"], ["2", "5"]])
end
it "groups must abort when isolate_count is out of bounds" do
expect do
call(3, single_process: [/1/], isolate_count: 3)
end.to raise_error(
"Number of isolated processes must be >= total number of processes"
)
end
context 'specify_groups' do
it "groups with one spec" do
expect(call(3, specify_groups: '1')).to eq([["1"], ["2", "5"], ["3", "4"]])
end
it "groups with multiple specs in one process" do
expect(call(3, specify_groups: '3,1')).to eq([["3", "1"], ["5"], ["2", "4"]])
end
it "groups with multiple specs and multiple processes" do
expect(call(3, specify_groups: '1,2|4')).to eq([["1", "2"], ["4"], ["3", "5"]])
end
it "aborts when number of specs is higher than number of processes" do
expect do
call(3, specify_groups: '1|2|3|4')
end.to raise_error(
"Number of processes separated by pipe must be less than or equal to the total number of processes"
)
end
it "aborts when spec passed in doesn't match existing specs" do
expect do
call(3, specify_groups: '1|2|6')
end.to raise_error(
"Could not find [\"6\"] from --specify-groups in the selected files & folders"
)
end
it "aborts when number of specs is equal to number of processes and not all specs are used" do
expect do
call(3, specify_groups: '1|2|3')
end.to raise_error(/The specs that aren't run:\n\["4", "5"\]/)
end
it "does not abort when the every single spec is specified" do
expect(call(3, specify_groups: '1,2|3,4|5')).to eq([["1", "2"], ["3", "4"], ["5"]])
end
it "can read from stdin" do
allow($stdin).to receive(:read).and_return("3,1\n")
expect(call(3, specify_groups: '-')).to eq([["3", "1"], ["5"], ["2", "4"]])
end
end
end
describe '.by_scenarios' do
let(:feature_file) { double 'file' }
it 'splits a feature into individual scenarios' do
expect(ParallelTests::Cucumber::Scenarios).to receive(:all).and_return({ 'feature_file:3' => 1 })
ParallelTests::Grouper.by_scenarios([feature_file], 1)
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/pids_spec.rb 0000664 0000000 0000000 00000001302 15043316274 0023005 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
RSpec.describe ParallelTests::Pids do
let(:file_path) { Tempfile.new('pidfile').path }
subject { described_class.new(file_path) }
before do
subject.send(:clear)
subject.add(123)
subject.add(456)
end
describe '#add' do
specify do
subject.add(789)
expect(subject.all).to eq([123, 456, 789])
end
end
describe '#delete' do
specify do
subject.add(101)
subject.delete(123)
expect(subject.all).to eq([456, 101])
end
end
describe '#count' do
specify { expect(subject.count).to eq(2) }
end
describe '#all' do
specify { expect(subject.all).to eq([123, 456]) }
end
end
parallel_tests-5.4.0/spec/parallel_tests/rspec/ 0000775 0000000 0000000 00000000000 15043316274 0021627 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/parallel_tests/rspec/failures_logger_spec.rb 0000664 0000000 0000000 00000000631 15043316274 0026337 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ParallelTests::RSpec::FailuresLogger do
let(:output) { OutputLogger.new([]) }
let(:logger) { ParallelTests::RSpec::FailuresLogger.new(output) }
it "prints failures" do
logger.dump_summary(double(failed_examples: [1], colorized_rerun_commands: "HEYHO"))
expect(output.output).to eq(
[
"HEYHO\n"
]
)
end
end
parallel_tests-5.4.0/spec/parallel_tests/rspec/logger_base_spec.rb 0000664 0000000 0000000 00000001457 15043316274 0025446 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ParallelTests::RSpec::LoggerBase do
before do
@temp_file = Tempfile.open('xxx')
@logger = ParallelTests::RSpec::LoggerBase.new(@temp_file)
end
after do
@temp_file.close
end
describe 'on tests finished' do
it 'should respond to close' do
expect(@logger).to respond_to(:close)
end
it 'should close output' do
expect(@temp_file).to receive(:close)
@logger.close
end
it 'should not close stdout' do
@logger = ParallelTests::RSpec::LoggerBase.new($stdout)
expect($stdout).not_to receive(:close)
@logger.close
end
it 'should not close IO instance' do
io = double(IO)
@logger = ParallelTests::RSpec::LoggerBase.new(io)
@logger.close
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/rspec/runner_spec.rb 0000664 0000000 0000000 00000015456 15043316274 0024512 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
require "parallel_tests/rspec/runner"
describe ParallelTests::RSpec::Runner do
test_tests_in_groups(ParallelTests::RSpec::Runner, '_spec.rb')
describe '.run_tests' do
before do
allow(File).to receive(:file?).with('spec/spec.opts').and_return false
allow(File).to receive(:file?).with('spec/parallel_spec.opts').and_return false
allow(File).to receive(:file?).with('.rspec_parallel').and_return false
allow(ParallelTests).to receive(:bundler_enabled?).and_return false
end
def call(*args)
ParallelTests::RSpec::Runner.run_tests(*args)
end
it "runs command using nice when specified" do
ParallelTests.with_pid_file do
expect(ParallelTests::Test::Runner).to receive(:execute_command_and_capture_output) do |_a, b, _c|
expect(b.first(2)).to eq(["nice", "rspec"])
end
call('xxx', 1, 22, nice: true)
end
end
it "runs with color when called from cmdline" do
should_run_with ["rspec"], "--tty"
expect($stdout).to receive(:tty?).and_return true
call('xxx', 1, 22, {})
end
it "runs without color when not called from cmdline" do
should_not_run_with('--tty')
expect($stdout).to receive(:tty?).and_return false
call('xxx', 1, 22, {})
end
it "uses bin/rspec when present" do
allow(File).to receive(:exist?).with('bin/rspec').and_return true
should_run_with ParallelTests.with_ruby_binary("bin/rspec")
call('xxx', 1, 22, {})
end
it "uses no -O when no opts where found" do
allow(File).to receive(:file?).with('spec/spec.opts').and_return false
should_not_run_with 'spec/spec.opts'
call('xxx', 1, 22, {})
end
it "uses -O spec/parallel_spec.opts with rspec2" do
skip if RUBY_PLATFORM == "java" # FIXME: not sure why, but fails on travis
expect(File).to receive(:file?).with('spec/parallel_spec.opts').and_return true
allow(ParallelTests).to receive(:bundler_enabled?).and_return true
should_run_with ["bundle", "exec", "rspec"], "-O", "spec/parallel_spec.opts", "xxx"
call('xxx', 1, 22, {})
end
it "uses options passed in" do
should_run_with ["rspec"], "-f", "n"
call('xxx', 1, 22, test_options: ['-f', 'n'])
end
it "returns the output" do
expect(ParallelTests::RSpec::Runner).to receive(:execute_command).and_return x: 1
expect(call('xxx', 1, 22, {})).to eq({ x: 1 })
end
end
describe '.find_results' do
def call(*args)
ParallelTests::RSpec::Runner.find_results(*args)
end
it "finds multiple results in spec output" do
output = <<-OUT.gsub(/^ /, '')
....F...
..
failute fsddsfsd
...
ff.**..
0 examples, 0 failures, 0 pending
ff.**..
1 example, 1 failure, 1 pending
OUT
expect(call(output)).to eq(['0 examples, 0 failures, 0 pending', '1 example, 1 failure, 1 pending'])
end
it "does not mistakenly count 'pending' failures as real failures" do
output = <<-OUT.gsub(/^ /, '')
.....
Pending: (Failures listed here are expected and do not affect your suite's status)
1) Foo
Got 1 failure and 1 other error:
1.1) Failure/Error:
Bar
Baz
1.2) Failure/Error:
Bar
Baz
1 examples, 0 failures, 1 pending
OUT
expect(call(output)).to eq(['1 examples, 0 failures, 1 pending'])
end
end
describe ".find_tests" do
def call(*args)
ParallelTests::RSpec::Runner.send(:find_tests, *args)
end
it "finds turnip feature files" do
with_files(['a/test.feature']) do |root|
expect(call(["#{root}/"])).to eq(["#{root}/a/test.feature"])
end
end
it "doesn't find backup files with the same name as test files" do
with_files(['a/x_spec.rb', 'a/x_spec.rb.bak']) do |root|
expect(call(["#{root}/"])).to eq(["#{root}/a/x_spec.rb"])
end
end
end
describe '.summarize_results' do
context 'not on TTY device' do
before { allow($stdout).to receive(:tty?).and_return false }
it 'is not colourized' do
results = ParallelTests::RSpec::Runner.send(:summarize_results, ['1 example, 0 failures, 0 pendings'])
expect(results).to eq('1 example, 0 failures, 0 pendings')
end
end
context 'on TTY device' do
before { allow($stdout).to receive(:tty?).and_return true }
subject(:colorized_results) { ParallelTests::RSpec::Runner.send(:summarize_results, [result_string]) }
context 'when there are no pending or failed tests' do
let(:result_string) { '1 example, 0 failures, 0 pendings' }
it 'is green' do
expect(colorized_results).to eq("\e[32m#{result_string}\e[0m") # 32 is green
end
end
context 'when there is a pending test and no failed tests' do
let(:result_string) { '1 example, 0 failures, 1 pending' }
it 'is yellow' do
expect(colorized_results).to eq("\e[33m#{result_string}\e[0m") # 33 is yellow
end
end
context 'when there is a pending test and a failed test' do
let(:result_string) { '1 example, 1 failure, 1 pending' }
it 'is red' do
expect(colorized_results).to eq("\e[31m#{result_string}\e[0m") # 31 is red
end
end
context 'when there is no pending tests and a failed test' do
let(:result_string) { '1 example, 1 failure, 0 pendings' }
it 'is red' do
expect(colorized_results).to eq("\e[31m#{result_string}\e[0m") # 31 is red
end
end
end
end
describe ".command_with_seed" do
def call(*args)
base = ["ruby", "-Ilib:test", "test/minitest/test_minitest_unit.rb"]
result = ParallelTests::RSpec::Runner.command_with_seed([*base, *args], "555")
result[base.length..]
end
it "adds the randomized seed" do
expect(call).to eq(["--seed", "555"])
end
it "does not duplicate seed" do
expect(call("--seed", "123")).to eq(["--seed", "555"])
end
it "does not duplicate strange seeds" do
expect(call("--seed", "123asdasd")).to eq(["--seed", "555"])
end
it "does not match non seeds" do
expect(call("--seedling", "123")).to eq(["--seedling", "123", "--seed", "555"])
end
it "does not duplicate random" do
expect(call("--order", "random")).to eq(["--seed", "555"])
end
it "does not duplicate rand" do
expect(call("--order", "rand")).to eq(["--seed", "555"])
end
it "does not duplicate rand with seed" do
expect(call("--order", "rand:123")).to eq(["--seed", "555"])
end
it "does not duplicate random with seed" do
expect(call("--order", "random:123")).to eq(["--seed", "555"])
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/rspec/runtime_logger_spec.rb 0000664 0000000 0000000 00000006721 15043316274 0026216 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ParallelTests::RSpec::RuntimeLogger do
before do
# pretend we run in parallel or the logger will log nothing
ENV['TEST_ENV_NUMBER'] = ''
@clean_output = %r{^spec/foo.rb:[-.e\d]+$}m
end
def log_for_a_file(_options = {})
Tempfile.open('xxx') do |temp|
temp.close
f = File.open(temp.path, 'w')
logger = if block_given?
yield(f)
else
ParallelTests::RSpec::RuntimeLogger.new(f)
end
example = double(file_path: "#{Dir.pwd}/spec/foo.rb")
example = double(group: example)
logger.example_group_started example
logger.example_group_finished example
logger.start_dump
# f.close
return File.read(f.path)
end
end
it "logs runtime with relative paths" do
expect(log_for_a_file).to match(@clean_output)
end
it "does not log if we do not run in parallel" do
ENV.delete 'TEST_ENV_NUMBER'
expect(log_for_a_file).to eq("")
end
it "appends to a given file" do
result = log_for_a_file do |f|
f.write 'FooBar'
ParallelTests::RSpec::RuntimeLogger.new(f)
end
expect(result).to include('FooBar')
expect(result).to include('foo.rb')
end
it "overwrites a given path" do
result = log_for_a_file do |f|
f.write 'FooBar'
ParallelTests::RSpec::RuntimeLogger.new(f.path)
end
expect(result).not_to include('FooBar')
expect(result).to include('foo.rb')
end
context "integration" do
around do |example|
Dir.mktmpdir do |dir|
Dir.chdir(dir, &example)
end
end
def write(file, content)
FileUtils.mkdir_p(File.dirname(file))
File.write(file, content)
end
it "logs shared examples into the running files" do
write "spec/spec_helper.rb", <<-RUBY
shared_examples "foo" do
it "is slow" do
sleep 0.5
end
end
RUBY
["a", "b"].each do |letter|
write "spec/#{letter}_spec.rb", <<-RUBY
require 'spec_helper'
describe 'xxx' do
it_behaves_like "foo"
end
RUBY
end
system(
{ 'TEST_ENV_NUMBER' => '1' },
"rspec", "spec", "-I", Bundler.root.join("lib").to_s, "--format", "ParallelTests::RSpec::RuntimeLogger", "--out", "runtime.log"
) || raise("nope")
result = File.read("runtime.log")
expect(result).to match(%r{^spec/a_spec.rb:0.5})
expect(result).to match(%r{^spec/b_spec.rb:0.5})
expect(result).not_to include "spec_helper"
end
it "logs multiple describe blocks" do
write "spec/a_spec.rb", <<-RUBY
describe "xxx" do
it "is slow" do
sleep 0.5
end
end
describe "yyy" do
it "is slow" do
sleep 0.5
end
describe "yep" do
it "is slow" do
sleep 0.5
end
end
end
RUBY
write "spec/slower_spec.rb", <<-RUBY
describe "xxx" do
it "is slow" do
sleep 3
end
end
RUBY
system(
{ 'TEST_ENV_NUMBER' => '1' },
"rspec", "spec", "-I", Bundler.root.join("lib").to_s, "--format", "ParallelTests::RSpec::RuntimeLogger", "--out", "runtime.log"
) || raise("nope")
result = File.read("runtime.log")
expect(result).to start_with("spec/slower_spec.rb:3.0")
expect(result).to include "spec/a_spec.rb:1.5"
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/rspec/summary_logger_spec.rb 0000664 0000000 0000000 00000000655 15043316274 0026230 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ParallelTests::RSpec::SummaryLogger do
let(:output) { OutputLogger.new([]) }
let(:logger) { ParallelTests::RSpec::SummaryLogger.new(output) }
it "prints failing examples" do
logger.dump_failures(double(failure_notifications: [1], fully_formatted_failed_examples: "HEYHO"))
expect(output.output).to eq(
[
"HEYHO\n"
]
)
end
end
parallel_tests-5.4.0/spec/parallel_tests/rspec/verbose_logger_spec.rb 0000664 0000000 0000000 00000002411 15043316274 0026170 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ParallelTests::RSpec::VerboseLogger do
def run(command)
result = IO.popen(command, err: [:child, :out], &:read)
raise "FAILED: #{result}" unless $?.success?
result
end
it 'outputs verbose information' do
repo_root = Dir.pwd
use_temporary_directory do
# setup simple structure
FileUtils.mkdir "spec"
File.write "spec/foo_spec.rb", <<-RUBY
describe "Foo" do
it "foo" do
sleep 0.5
expect(true).to be(true)
end
end
RUBY
File.write "spec/bar_spec.rb", <<-RUBY
describe "Bar" do
it "bar" do
sleep 0.25111
expect(true).to be(true)
end
end
RUBY
result = run [
"ruby",
"#{repo_root}/bin/parallel_rspec",
"-n", "2",
"--",
"--format", "ParallelTests::RSpec::VerboseLogger",
"--"
]
expect(result).to match(/^\[\d+\] \[(1|2)\] \[STARTED\] Foo foo$/)
expect(result).to match(/^\[\d+\] \[(1|2)\] \[PASSED\] Foo foo$/)
expect(result).to match(/^\[\d+\] \[(1|2)\] \[STARTED\] Bar bar$/)
expect(result).to match(/^\[\d+\] \[(1|2)\] \[PASSED\] Bar bar$/)
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/spinach/ 0000775 0000000 0000000 00000000000 15043316274 0022140 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/parallel_tests/spinach/runner_spec.rb 0000664 0000000 0000000 00000000623 15043316274 0025011 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
require "parallel_tests/gherkin/runner_behaviour"
require "parallel_tests/spinach/runner"
describe ParallelTests::Spinach::Runner do
test_tests_in_groups(ParallelTests::Spinach::Runner, ".feature")
it_should_behave_like 'gherkin runners' do
let(:runner_name) { 'spinach' }
let(:runner_class) { ParallelTests::Spinach::Runner }
end
end
parallel_tests-5.4.0/spec/parallel_tests/tasks_spec.rb 0000664 0000000 0000000 00000021531 15043316274 0023201 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
require 'parallel_tests/tasks'
require 'rspec/support/spec/shell_out'
describe ParallelTests::Tasks do
describe ".parse_args" do
it "should return the count" do
args = { count: 2 }
expect(ParallelTests::Tasks.parse_args(args)).to eq([2, nil, nil, nil])
end
it "should default to the prefix" do
args = { count: "models" }
expect(ParallelTests::Tasks.parse_args(args)).to eq([nil, "models", nil, nil])
end
it "should return the count and pattern" do
args = { count: 2, pattern: "models" }
expect(ParallelTests::Tasks.parse_args(args)).to eq([2, "models", nil, nil])
end
it "should return the count, pattern, and options" do
args = { count: 2, pattern: "plain", options: "-p default" }
expect(ParallelTests::Tasks.parse_args(args)).to eq([2, "plain", "-p default", nil])
end
it "should return the count, pattern, and options" do
args = { count: 2, pattern: "plain", options: "-p default --group-by steps" }
expect(ParallelTests::Tasks.parse_args(args)).to eq([2, "plain", "-p default --group-by steps", nil])
end
it "should return the count, pattern, test options, and pass-through options" do
args = {
count: 2, pattern: "plain", options: "-p default --group-by steps",
pass_through: "--runtime-log /path/to/log"
}
expect(ParallelTests::Tasks.parse_args(args)).to eq(
[2, "plain", "-p default --group-by steps",
"--runtime-log /path/to/log"]
)
end
end
describe ".rails_env" do
include RSpec::Support::ShellOut
it "is test when nothing was set" do
expect(ParallelTests::Tasks.rails_env).to eq("test")
end
it "ignores RAILS_ENV since that is often set when rake is executed" do
with_env "RAILS_ENV" => "bar" do
expect(ParallelTests::Tasks.rails_env).to eq("test")
end
end
it "uses PARALLEL_RAILS_ENV" do
with_env "PARALLEL_RAILS_ENV" => "bar" do
expect(ParallelTests::Tasks.rails_env).to eq("bar")
end
end
end
describe ".run_in_parallel" do
let(:full_path) { File.expand_path('../../bin/parallel_test', __dir__) }
it "has the executable" do
expect(File.file?(full_path)).to eq(true)
expect(File.executable?(full_path)).to eq(true) unless Gem.win_platform?
end
it "runs command in parallel" do
expect(ParallelTests::Tasks).to receive(:system)
.with(*ParallelTests.with_ruby_binary(full_path), '--exec', 'echo')
.and_return true
ParallelTests::Tasks.run_in_parallel(["echo"])
end
it "runs command with :count option" do
expect(ParallelTests::Tasks).to receive(:system)
.with(*ParallelTests.with_ruby_binary(full_path), '--exec', 'echo', '-n', 123)
.and_return true
ParallelTests::Tasks.run_in_parallel(["echo"], count: 123)
end
it "runs without -n with blank :count option" do
expect(ParallelTests::Tasks).to receive(:system)
.with(*ParallelTests.with_ruby_binary(full_path), '--exec', 'echo')
.and_return true
ParallelTests::Tasks.run_in_parallel(["echo"], count: "")
end
it "runs command with :non_parallel option" do
expect(ParallelTests::Tasks).to receive(:system)
.with(*ParallelTests.with_ruby_binary(full_path), '--exec', 'echo', '--non-parallel')
.and_return true
ParallelTests::Tasks.run_in_parallel(["echo"], non_parallel: true)
end
it "runs aborts if the command fails" do
expect(ParallelTests::Tasks).to receive(:system).and_return false
expect(ParallelTests::Tasks).to receive(:abort).and_return false
ParallelTests::Tasks.run_in_parallel(["echo"])
end
end
describe ".suppress_output", unless: Gem.win_platform? do
def call(command, grep)
# Explicitly run as a parameter to /bin/bash to simulate how
# the command will be run by parallel_test --exec
# This also tests shell escaping of single quotes
shell_command = [
'/bin/bash',
'-c',
Shellwords.shelljoin(ParallelTests::Tasks.suppress_output(command, grep))
]
result = IO.popen(shell_command, &:read)
[result, $?.success?]
end
context "with pipefail supported" do
before :all do
unless system("/bin/bash", "-c", "set -o pipefail 2>/dev/null")
skip "pipefail is not supported on your system"
end
end
it "should hide offending lines" do
expect(call(["echo", "123"], "123")).to eq(["", true])
end
it "should not hide other lines" do
expect(call(["echo", "124"], "123")).to eq(["124\n", true])
end
it "should fail if command fails and the pattern matches" do
expect(call(['/bin/bash', '-c', 'echo 123 && false'], "123")).to eq(["", false])
end
it "should fail if command fails and the pattern fails" do
expect(call(['/bin/bash', '-c', 'echo 124 && false'], "123")).to eq(["124\n", false])
end
end
context "without pipefail supported" do
before do
expect(ParallelTests::Tasks).to receive(:system).with(
'/bin/bash', '-c',
'set -o pipefail 2>/dev/null'
).and_return false
end
it "should not filter and succeed" do
expect(call(["echo", "123"], "123")).to eq(["123\n", true])
end
it "should not filter and fail" do
expect(call(['/bin/bash', '-c', 'echo 123 && false'], "123")).to eq(["123\n", false])
end
end
end
describe ".suppress_schema_load_output" do
before do
allow(ParallelTests::Tasks).to receive(:suppress_output)
end
it 'should call suppress output with command' do
ParallelTests::Tasks.suppress_schema_load_output('command')
expect(ParallelTests::Tasks).to have_received(:suppress_output).with('command', "^ ->\\|^-- ")
end
end
describe ".check_for_pending_migrations" do
after do
Rake.application.instance_variable_get('@tasks').delete("db:abort_if_pending_migrations")
Rake.application.instance_variable_get('@tasks').delete("app:db:abort_if_pending_migrations")
end
it "should do nothing if pending migrations is no defined" do
ParallelTests::Tasks.check_for_pending_migrations
end
it "should run pending migrations is task is defined" do
foo = 1
Rake::Task.define_task("db:abort_if_pending_migrations") do
foo = 2
end
ParallelTests::Tasks.check_for_pending_migrations
expect(foo).to eq(2)
end
it "should run pending migrations is app task is defined" do
foo = 1
Rake::Task.define_task("app:db:abort_if_pending_migrations") do
foo = 2
end
ParallelTests::Tasks.check_for_pending_migrations
expect(foo).to eq(2)
end
it "should not execute the task twice" do
foo = 1
Rake::Task.define_task("db:abort_if_pending_migrations") do
foo += 1
end
ParallelTests::Tasks.check_for_pending_migrations
ParallelTests::Tasks.check_for_pending_migrations
expect(foo).to eq(2)
end
end
describe ".purge_before_load" do
context 'ActiveRecord < 4.2.0' do
before do
stub_const('ActiveRecord', double(version: Gem::Version.new('3.2.1')))
end
it "should return nil for ActiveRecord < 4.2.0" do
expect(ParallelTests::Tasks.purge_before_load).to eq nil
end
end
context 'ActiveRecord > 4.2.0' do
before do
stub_const('ActiveRecord', double(version: Gem::Version.new('4.2.8')))
end
it "should return db:purge when defined" do
allow(Rake::Task).to receive(:task_defined?).with('db:purge') { true }
expect(ParallelTests::Tasks.purge_before_load).to eq 'db:purge'
end
it "should return app:db:purge when db:purge is not defined" do
allow(Rake::Task).to receive(:task_defined?).with('db:purge') { false }
expect(ParallelTests::Tasks.purge_before_load).to eq 'app:db:purge'
end
end
end
describe ".build_run_command" do
it "builds simple command" do
command = ParallelTests::Tasks.build_run_command("test", {})
command.shift 2 if command.include?("--") # windows prefixes ruby executable
expect(command).to eq [
"#{Dir.pwd}/bin/parallel_test", "test", "--type", "test"
]
end
it "fails on unknown" do
expect { ParallelTests::Tasks.build_run_command("foo", {}) }.to raise_error(KeyError)
end
it "builds with all arguments" do
command = ParallelTests::Tasks.build_run_command(
"test",
count: 1, pattern: "foo", options: "bar", pass_through: "baz baz"
)
command.shift 2 if command.include?("--") # windows prefixes ruby executable
expect(command).to eq [
"#{Dir.pwd}/bin/parallel_test", "test", "--type", "test",
"-n", "1", "--pattern", "foo", "--test-options", "bar", "baz", "baz"
]
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/test/ 0000775 0000000 0000000 00000000000 15043316274 0021472 5 ustar 00root root 0000000 0000000 parallel_tests-5.4.0/spec/parallel_tests/test/runner_spec.rb 0000664 0000000 0000000 00000052521 15043316274 0024347 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
require "parallel_tests/test/runner"
describe ParallelTests::Test::Runner do
test_tests_in_groups(ParallelTests::Test::Runner, '_test.rb')
test_tests_in_groups(ParallelTests::Test::Runner, '_spec.rb')
describe ".run_tests" do
def call(*args)
ParallelTests::Test::Runner.run_tests(*args)
end
it "allows to override runner executable via PARALLEL_TESTS_EXECUTABLE" do
ENV['PARALLEL_TESTS_EXECUTABLE'] = 'script/custom_rspec'
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |a, _, _, _d|
expect(a).to include("script/custom_rspec")
end
call(['xxx'], 1, 22, {})
ENV['PARALLEL_TESTS_EXECUTABLE'] = 'ruby -Icustom_option script/custom_rspec'
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |a, _, _, _d|
expect(a).to include("ruby", "-Icustom_option", "script/custom_rspec")
end
call(['xxx'], 1, 22, {})
end
it "uses options" do
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |a, _, _, _d|
expect(a).to eq(["ruby", "-Itest", "-e", "%w[xxx].each { |f| require %{./\#{f}} }", "--", "-v"])
end
call(['xxx'], 1, 22, test_options: '-v')
end
it "returns the output" do
expect(ParallelTests::Test::Runner).to receive(:execute_command).and_return(x: 1)
expect(call(['xxx'], 1, 22, {})).to eq({ x: 1 })
end
end
describe ".test_in_groups" do
def call(*args)
ParallelTests::Test::Runner.tests_in_groups(*args)
end
it "raises when passed invalid group" do
expect { call([], 1, group_by: :sdjhfdfdjs) }.to raise_error(ArgumentError)
end
it "uses given when passed found" do
result = (Gem.win_platform? && RUBY_VERSION < "3.3.0" ? [["a", "b"], ["c"]] : [["a", "c"], ["b"]])
expect(call(["a", "b", "c"], 2, group_by: :found)).to eq(result)
end
context "when passed no group" do
it "sort by file size" do
expect(File).to receive(:stat).with("a").and_return 1
expect(File).to receive(:stat).with("b").and_return 1
expect(File).to receive(:stat).with("c").and_return 3
call(["a", "b", "c"], 2)
end
it "sorts by runtime when runtime is available" do
expect(ParallelTests::Test::Runner).to receive(:puts).with("Using recorded test runtime")
expect(ParallelTests::Test::Runner).to receive(:runtimes).and_return("a" => 1, "b" => 1, "c" => 3)
expect(call(["a", "b", "c"], 2)).to eq([["c"], ["a", "b"]])
end
it "sorts by filesize when there are no files" do
expect(ParallelTests::Test::Runner).to receive(:puts).never
expect(ParallelTests::Test::Runner).to receive(:runtimes).and_return({})
expect(call([], 2)).to eq([[], []])
end
it "sorts by filesize when runtime is too little" do
expect(ParallelTests::Test::Runner).not_to receive(:puts)
expect(ParallelTests::Test::Runner).to receive(:runtimes).and_return(["a:1"])
expect(File).to receive(:stat).with("a").and_return 1
expect(File).to receive(:stat).with("b").and_return 1
expect(File).to receive(:stat).with("c").and_return 3
call(["a", "b", "c"], 2)
end
end
context "when passed runtime" do
around { |test| Dir.mktmpdir { |dir| Dir.chdir(dir, &test) } }
before do
["aaa", "bbb", "ccc", "ddd"].each { |f| File.write(f, f) }
FileUtils.mkdir("tmp")
end
it "fails when there is no log" do
expect { call(["aaa"], 3, group_by: :runtime) }.to raise_error(Errno::ENOENT)
end
it "fails when there is too little log" do
File.write("tmp/parallel_runtime_test.log", "xxx:123\nyyy:123\naaa:123")
expect { call(["aaa", "bbb", "ccc"], 3, group_by: :runtime) }.to raise_error(ParallelTests::Test::Runner::RuntimeLogTooSmallError)
end
it "groups a lot of missing files when allow-missing is high" do
File.write("tmp/parallel_runtime_test.log", "xxx:123\nyyy:123\naaa:123")
call(["aaa", "bbb", "ccc"], 3, group_by: :runtime, allowed_missing_percent: 80)
end
it "groups when there is enough log" do
File.write("tmp/parallel_runtime_test.log", "xxx:123\nbbb:123\naaa:123")
call(["aaa", "bbb", "ccc"], 3, group_by: :runtime)
end
it "groups when test name contains colons" do
File.write("tmp/parallel_runtime_test.log", "ccc[1:2:3]:1\nbbb[1:2:3]:2\naaa[1:2:3]:3")
expect(
call(
["aaa[1:2:3]", "bbb[1:2:3]", "ccc[1:2:3]"], 2,
group_by: :runtime
)
).to match_array([["aaa[1:2:3]"], ["bbb[1:2:3]", "ccc[1:2:3]"]])
end
it "groups when not even statistic" do
File.write("tmp/parallel_runtime_test.log", "aaa:1\nbbb:1\nccc:8")
expect(call(["aaa", "bbb", "ccc"], 2, group_by: :runtime)).to match_array([["aaa", "bbb"], ["ccc"]])
end
it "groups with average for missing" do
File.write("tmp/parallel_runtime_test.log", "xxx:123\nbbb:10\nccc:1")
expect(call(["aaa", "bbb", "ccc", "ddd"], 2, group_by: :runtime)).to eq([["bbb", "ccc"], ["aaa", "ddd"]])
end
it "groups with unknown-runtime for missing" do
File.write("tmp/parallel_runtime_test.log", "xxx:123\nbbb:10\nccc:1")
expect(
call(["aaa", "bbb", "ccc", "ddd"], 2, group_by: :runtime, unknown_runtime: 0.0)
).to eq([["bbb"], ["aaa", "ccc", "ddd"]])
end
it "groups by single_process pattern and then via size" do
expect(ParallelTests::Test::Runner).to receive(:runtimes)
.and_return({ "aaa" => 5, "bbb" => 2, "ccc" => 1, "ddd" => 1 })
result = call(["aaa", "aaa2", "bbb", "ccc", "ddd"], 3, single_process: [/^a.a/], group_by: :runtime)
expect(result).to eq([["aaa", "aaa2"], ["bbb"], ["ccc", "ddd"]])
end
it "groups by size and adds isolated separately" do
skip if RUBY_PLATFORM == "java"
expect(ParallelTests::Test::Runner).to receive(:runtimes)
.and_return({ "aaa" => 0, "bbb" => 3, "ccc" => 1, "ddd" => 2 })
result = call(
["aaa", "bbb", "ccc", "ddd", "eee"], 3, isolate: true, single_process: [/^aaa/], group_by: :runtime
)
isolated, *groups = result
expect(isolated).to eq(["aaa"])
actual = groups.to_set(&:to_set)
# both eee and ccs are the same size, so either can be in either group
valid_combinations = [
[["bbb", "eee"], ["ccc", "ddd"]].to_set(&:to_set),
[["bbb", "ccc"], ["eee", "ddd"]].to_set(&:to_set)
]
expect(valid_combinations).to include(actual)
end
it "groups by size and use specified number of isolation groups" do
skip if RUBY_PLATFORM == "java"
expect(ParallelTests::Test::Runner).to receive(:runtimes)
.and_return({ "aaa1" => 1, "aaa2" => 3, "aaa3" => 2, "bbb" => 3, "ccc" => 1, "ddd" => 2 })
result = call(
["aaa1", "aaa2", "aaa3", "bbb", "ccc", "ddd", "eee"], 4, isolate_count: 2, single_process: [/^aaa/], group_by: :runtime
)
isolated_1, isolated_2, *groups = result
expect(isolated_1).to eq(["aaa2"])
expect(isolated_2).to eq(["aaa1", "aaa3"])
actual = groups.to_set(&:to_set)
# both eee and ccs are the same size, so either can be in either group
valid_combinations = [
[["bbb", "eee"], ["ccc", "ddd"]].to_set(&:to_set),
[["bbb", "ccc"], ["eee", "ddd"]].to_set(&:to_set)
]
expect(valid_combinations).to include(actual)
end
it 'groups by size and uses specified groups of specs in specific order in specific processes' do
skip if RUBY_PLATFORM == "java"
expect(ParallelTests::Test::Runner).to receive(:runtimes)
.and_return({ "aaa1" => 1, "aaa2" => 1, "aaa3" => 2, "bbb" => 3, "ccc" => 1, "ddd" => 2, "eee" => 1 })
result = call(
["aaa1", "aaa2", "aaa3", "bbb", "ccc", "ddd", "eee"], 4, specify_groups: 'aaa2,aaa1|bbb', group_by: :runtime
)
specify_groups_1, specify_groups_2, *groups = result
expect(specify_groups_1).to eq(["aaa2", "aaa1"])
expect(specify_groups_2).to eq(["bbb"])
actual = groups.to_set(&:to_set)
# both eee and ccs are the same size, so either can be in either group
valid_combinations = [
[["aaa3", "ccc"], ["ddd", "eee"]].to_set(&:to_set),
[["aaa3", "eee"], ["ddd", "ccc"]].to_set(&:to_set)
]
expect(valid_combinations).to include(actual)
end
end
end
describe ".find_results" do
def call(*args)
ParallelTests::Test::Runner.find_results(*args)
end
it "finds multiple results in test output" do
output = <<~OUT
Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
Started
..............
Finished in 0.145069 seconds.
10 tests, 20 assertions, 0 failures, 0 errors
Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
Started
..............
Finished in 0.145069 seconds.
14 tests, 20 assertions, 0 failures, 0 errors
OUT
expect(call(output)).to eq(
[
'10 tests, 20 assertions, 0 failures, 0 errors',
'14 tests, 20 assertions, 0 failures, 0 errors'
]
)
end
it "ignores color-codes" do
output = <<~EOF
10 tests, 20 assertions, 0 \e[31mfailures, 0 errors
EOF
expect(call(output)).to eq(['10 tests, 20 assertions, 0 failures, 0 errors'])
end
it "splits lines with Windows line separators" do
output = "10 tests, 20 assertions, 0 failures, 0 errors\r\n15 tests, 25 assertions, 0 failures, 0 errors"
expect(call(output)).to eq(
[
"10 tests, 20 assertions, 0 failures, 0 errors",
"15 tests, 25 assertions, 0 failures, 0 errors"
]
)
end
end
describe ".find_tests" do
def call(*args)
ParallelTests::Test::Runner.send(:find_tests, *args)
end
it "finds test in folders with appended /" do
with_files(['b/a_test.rb']) do |root|
expect(call(["#{root}/"]).sort).to eq(["#{root}/b/a_test.rb"])
end
end
it "finds test files nested in symlinked folders" do
with_files(['a/a_test.rb', 'b/b_test.rb']) do |root|
File.symlink("#{root}/a", "#{root}/b/link")
expect(call(["#{root}/b"]).sort).to eq(
[
"#{root}/b/b_test.rb",
"#{root}/b/link/a_test.rb"
]
)
end
end
it "finds test files but ignores those in symlinked folders" do
skip if RUBY_PLATFORM == "java" || Gem.win_platform?
with_files(['a/a_test.rb', 'b/b_test.rb']) do |root|
File.symlink("#{root}/a", "#{root}/b/link")
expect(call(["#{root}/b"], symlinks: false).sort).to eq(["#{root}/b/b_test.rb"])
end
end
it "finds test files nested in different folders" do
with_files(['a/a_test.rb', 'b/b_test.rb', 'c/c_test.rb']) do |root|
expect(call(["#{root}/a", "#{root}/b"]).sort).to eq(
[
"#{root}/a/a_test.rb",
"#{root}/b/b_test.rb"
]
)
end
end
it "only finds tests in folders" do
with_files(['a/a_test.rb', 'a/test.rb', 'a/test_helper.rb']) do |root|
expect(call(["#{root}/a"]).sort).to eq(
[
"#{root}/a/a_test.rb"
]
)
end
end
it "finds tests in nested folders" do
with_files(['a/b/c/d/a_test.rb']) do |root|
expect(call(["#{root}/a"]).sort).to eq(
[
"#{root}/a/b/c/d/a_test.rb"
]
)
end
end
it "does not expand paths" do
with_files(['a/x_test.rb']) do |root|
Dir.chdir root do
expect(call(['a']).sort).to eq(
[
"a/x_test.rb"
]
)
end
end
end
it "finds test files in folders by pattern" do
with_files(['a/x_test.rb', 'a/y_test.rb', 'a/z_test.rb']) do |root|
Dir.chdir root do
expect(call(["a"], pattern: %r{^a/(y|z)_test}).sort).to eq(
[
"a/y_test.rb",
"a/z_test.rb"
]
)
end
end
end
it "finds test files in folders using suffix and overriding built in suffix" do
with_files(['a/x_test.rb', 'a/y_test.rb', 'a/z_other.rb', 'a/x_different.rb']) do |root|
Dir.chdir root do
expect(call(["a"], suffix: /_(test|other)\.rb$/).sort).to eq(
[
"a/x_test.rb",
"a/y_test.rb",
"a/z_other.rb"
]
)
end
end
end
it "doesn't find backup files with the same name as test files" do
with_files(['a/x_test.rb', 'a/x_test.rb.bak']) do |root|
expect(call(["#{root}/"])).to eq(
[
"#{root}/a/x_test.rb"
]
)
end
end
it "finds minispec files" do
with_files(['a/x_spec.rb']) do |root|
expect(call(["#{root}/"])).to eq(
[
"#{root}/a/x_spec.rb"
]
)
end
end
it "finds nothing if I pass nothing" do
expect(call(nil)).to eq([])
end
it "finds nothing if I pass nothing (empty array)" do
expect(call([])).to eq([])
end
it "keeps invalid files" do
expect(call(['baz'])).to eq(['baz'])
end
it "discards duplicates" do
expect(call(['baz', 'baz'])).to eq(['baz'])
end
it "keeps duplicates when allow_duplicates" do
expect(call(['baz', 'baz'], allow_duplicates: true)).to eq(['baz', 'baz'])
end
end
describe ".summarize_results" do
def call(*args)
ParallelTests::Test::Runner.summarize_results(*args)
end
it "adds results" do
expect(call(['1 foo 3 bar', '2 foo 5 bar'])).to eq('8 bars, 3 foos')
end
it "adds results with braces" do
expect(call(['1 foo(s) 3 bar(s)', '2 foo 5 bar'])).to eq('8 bars, 3 foos')
end
it "adds same results with plurals" do
expect(call(['1 foo 3 bar', '2 foos 5 bar'])).to eq('8 bars, 3 foos')
end
it "adds non-similar results" do
expect(call(['1 xxx 2 yyy', '1 xxx 2 zzz'])).to eq('2 xxxs, 2 yyys, 2 zzzs')
end
it "does not pluralize 1" do
expect(call(['1 xxx 2 yyy'])).to eq('1 xxx, 2 yyys')
end
end
describe ".execute_command" do
def call(*args)
ParallelTests::Test::Runner.execute_command(*args)
end
let(:new_line_char) { Gem.win_platform? ? "\r\n" : "\n" }
def capture_output
$stdout = StringIO.new
$stderr = StringIO.new
yield
[$stdout.string, $stderr.string]
ensure
$stdout = STDOUT
$stderr = STDERR
end
def run_with_file(content)
ParallelTests.with_pid_file do
capture_output do
Tempfile.open("xxx") do |f|
f.write(content)
f.flush
yield f.path
end
end
end
end
it "sets process number to 2 for 1" do
run_with_file("puts ENV['TEST_ENV_NUMBER']") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result[:stdout].chomp).to eq '2'
expect(result[:exit_status]).to eq 0
end
end
it "sets process number to '' for 0" do
run_with_file("puts ENV['TEST_ENV_NUMBER'].inspect") do |path|
result = call(["ruby", path], 0, 4, {})
expect(result[:stdout].chomp).to eq '""'
expect(result[:exit_status]).to eq 0
end
end
it "sets process number to 1 for 0 if requested" do
run_with_file("puts ENV['TEST_ENV_NUMBER']") do |path|
result = call(["ruby", path], 0, 4, first_is_1: true)
expect(result[:stdout].chomp).to eq '1'
expect(result[:exit_status]).to eq 0
end
end
it 'sets PARALLEL_TEST_GROUPS so child processes know that they are being run under parallel_tests' do
run_with_file("puts ENV['PARALLEL_TEST_GROUPS']") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result[:stdout].chomp).to eq('4')
expect(result[:exit_status]).to eq(0)
end
end
it "skips reads from stdin" do
skip "hangs on normal ruby, works on jruby" unless RUBY_PLATFORM == "java"
run_with_file("$stdin.read; puts 123") do |path|
result = call(["ruby", path], 1, 2, {})
expect(result).to include(
{
stdout: "123\n",
exit_status: 0
}
)
end
end
it "waits for process to finish" do
run_with_file("sleep 0.5; puts 123; sleep 0.5; puts 345") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result[:stdout].lines.map(&:chomp)).to eq ['123', '345']
expect(result[:exit_status]).to eq 0
end
end
it "prints output while running" do
skip "too slow" if RUBY_PLATFORM == " java"
run_with_file("$stdout.sync = true; puts 123; sleep 0.1; print 345; sleep 0.1; puts 567") do |path|
received = +""
allow($stdout).to receive(:print) do |x|
received << x.strip
end
result = call(["ruby", path], 1, 4, {})
expect(received).to eq("123345567")
expect(result[:stdout].lines.map(&:chomp)).to eq ['123', '345567']
expect(result[:exit_status]).to eq 0
end
end
it "works with synced stdout" do
run_with_file("$stdout.sync = true; puts 123; sleep 0.1; puts 345") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result[:stdout].lines.map(&:chomp)).to eq ['123', '345']
expect(result[:exit_status]).to eq 0
end
end
it "does not print to stdout with :serialize_stdout" do
run_with_file("puts 123") do |path|
expect($stdout).not_to receive(:print)
result = call(["ruby", path], 1, 4, serialize_stdout: true)
expect(result[:stdout].chomp).to eq '123'
expect(result[:exit_status]).to eq 0
end
end
it "adds test env number to stdout with :prefix_output_with_test_env_number" do
run_with_file("puts 123") do |path|
expect($stdout).to receive(:print).with("[TEST GROUP 2] 123#{new_line_char}")
result = call(["ruby", path], 1, 4, prefix_output_with_test_env_number: true)
expect(result[:stdout].chomp).to eq '123'
expect(result[:exit_status]).to eq 0
end
end
it "does not add test env number to stdout without :prefix_output_with_test_env_number" do
run_with_file("puts 123") do |path|
expect($stdout).to receive(:print).with("123#{new_line_char}")
result = call(["ruby", path], 1, 4, prefix_output_with_test_env_number: false)
expect(result[:stdout].chomp).to eq '123'
expect(result[:exit_status]).to eq 0
end
end
it "returns correct exit status" do
run_with_file("puts 123; exit 5") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result[:stdout].chomp).to eq '123'
expect(result[:exit_status]).to eq 5
end
end
it "prints each stream to the correct stream" do
skip "open3"
_out, err = run_with_file("puts 123 ; $stderr.puts 345 ; exit 5") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result).to include(
stdout: "123\n",
exit_status: 5
)
end
expect(err).to eq("345\n")
end
it "uses a lower priority process when the nice option is used", unless: Gem.win_platform? do
priority_cmd = "puts Process.getpriority(Process::PRIO_PROCESS, 0)"
priority_without_nice = run_with_file(priority_cmd) { |cmd| call(["ruby", cmd], 1, 4, {}) }.first.to_i
priority_with_nice = run_with_file(priority_cmd) { |cmd| call(["ruby", cmd], 1, 4, nice: true) }.first.to_i
expect(priority_without_nice).to be < priority_with_nice
end
it "returns command used" do
run_with_file("puts 123; exit 5") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result).to include(command: ["ruby", path])
end
end
it "allows using TEST_ENV_NUMBER in arguments" do
run_with_file("puts ARGV") do |path|
result = call(["ruby", path, "$TEST_ENV_NUMBER"], 1, 4, {})
expect(result[:stdout].chomp).to eq '2'
expect(result[:exit_status]).to eq 0
end
end
describe "rspec seed" do
it "includes seed when provided" do
run_with_file("puts 'Run options: --seed 555'") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result).to include(seed: "555")
end
end
it "seed is nil when not provided" do
run_with_file("puts 555") do |path|
result = call(["ruby", path], 1, 4, {})
expect(result).to include(seed: nil)
end
end
end
end
describe ".command_with_seed" do
def call(*args)
base = ["ruby", "-Ilib:test", "test/minitest/test_minitest_unit.rb"]
result = ParallelTests::Test::Runner.command_with_seed([*base, *args], "555")
result[base.length..]
end
it "adds the randomized seed" do
expect(call).to eq(["--seed", "555"])
end
it "does not duplicate seed" do
expect(call("--seed", "123")).to eq(["--seed", "555"])
end
it "does not duplicate strange seeds" do
expect(call("--seed", "123asdasd")).to eq(["--seed", "555"])
end
it "does not match non seeds" do
expect(call("--seedling", "123")).to eq(["--seedling", "123", "--seed", "555"])
end
end
end
parallel_tests-5.4.0/spec/parallel_tests/test/runtime_logger_spec.rb 0000664 0000000 0000000 00000002476 15043316274 0026064 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'spec_helper'
describe ParallelTests::Test::RuntimeLogger do
def run(command)
result = IO.popen(command, err: [:child, :out], &:read)
raise "FAILED: #{result}" unless $?.success?
end
def run_tests(repo_root_dir)
run ["ruby", "#{repo_root_dir}/bin/parallel_test", "test", "-n", "2"]
end
it "writes a correct log on minitest-5" do
skip if RUBY_PLATFORM == "java" # just too slow ...
repo_root = Dir.pwd
use_temporary_directory do
# setup simple structure
FileUtils.mkdir "test"
2.times do |i|
File.write("test/#{i}_test.rb", <<-RUBY)
require 'minitest/autorun'
require 'parallel_tests/test/runtime_logger'
class Foo#{i} < Minitest::Test
def test_foo
sleep 0.5
assert true
end
end
class Bar#{i} < Minitest::Test
def test_foo
sleep 0.25111
assert true
end
end
RUBY
end
run_tests(repo_root)
# log looking good ?
lines = File.read("tmp/parallel_runtime_test.log").split("\n").sort.map { |x| x.sub(/\d$/, "") }
expect(lines).to eq(
[
"test/0_test.rb:0.7",
"test/1_test.rb:0.7"
]
)
end
end
end
parallel_tests-5.4.0/spec/parallel_tests_spec.rb 0000664 0000000 0000000 00000015116 15043316274 0022056 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "spec_helper"
describe ParallelTests do
describe ".determine_number_of_processes" do
before do
allow(Parallel).to receive(:processor_count).and_return 20
end
def call(count)
ParallelTests.determine_number_of_processes(count)
end
it "uses the given count if set" do
expect(call('5')).to eq(5)
end
it "uses the processor count from Parallel" do
expect(call(nil)).to eq(20)
end
it "uses the processor count from ENV before Parallel" do
ENV['PARALLEL_TEST_PROCESSORS'] = '22'
expect(call(nil)).to eq(22)
end
it "does not use blank count" do
expect(call(' ')).to eq(20)
end
it "does not use blank env" do
ENV['PARALLEL_TEST_PROCESSORS'] = ' '
expect(call(nil)).to eq(20)
end
end
describe ".determine_multiple" do
let(:default_multiple) { 1.0 }
before do
allow(Parallel).to receive(:multiple).and_return(default_multiple)
end
after do
ENV.delete('PARALLEL_TEST_MULTIPLY_PROCESSES')
end
def call(multiple)
ParallelTests.determine_multiple(multiple)
end
it "uses the given multiple if set" do
expect(call('.5')).to eq(0.5)
end
it "uses the processor multiple from Parallel" do
expect(call(nil)).to eq(default_multiple)
end
it "uses the processor multiple from ENV before Parallel" do
ENV['PARALLEL_TEST_MULTIPLY_PROCESSES'] = '0.75'
expect(call(nil)).to eq(0.75)
end
it "does not use blank multiple" do
expect(call(' ')).to eq(default_multiple)
end
it "does not use blank env" do
ENV['PARALLEL_TEST_MULTIPLY_PROCESSES'] = ' '
expect(call(nil)).to eq(default_multiple)
end
end
describe ".bundler_enabled?" do
before do
allow(Object).to receive(:const_defined?).with(:Bundler).and_return false
end
it "is false" do
use_temporary_directory do
expect(ParallelTests.send(:bundler_enabled?)).to eq(false)
end
end
it "is true when there is a constant called Bundler" do
use_temporary_directory do
allow(Object).to receive(:const_defined?).with(:Bundler).and_return true
expect(ParallelTests.send(:bundler_enabled?)).to eq(true)
end
end
it "is true when there is a Gemfile" do
use_temporary_directory do
FileUtils.touch("Gemfile")
expect(ParallelTests.send(:bundler_enabled?)).to eq(true)
end
end
it "is true when there is a Gemfile in the parent directory" do
use_temporary_directory do
FileUtils.mkdir "nested"
Dir.chdir "nested" do
FileUtils.touch(File.join("..", "Gemfile"))
expect(ParallelTests.send(:bundler_enabled?)).to eq(true)
end
end
end
end
describe ".wait_for_other_processes_to_finish" do
around do |example|
ParallelTests.with_pid_file do
example.run
end
end
def with_running_processes(count, wait = 0.2)
count.times { |x| ParallelTests.pids.add(x) }
sleep 0.1
yield
ensure
sleep wait # make sure the threads have finished
end
it "does not wait if not run in parallel" do
expect(ParallelTests).not_to receive(:sleep)
ParallelTests.wait_for_other_processes_to_finish
end
it "stops if only itself is running" do
ParallelTests.pids.add(123)
expect(ParallelTests).not_to receive(:sleep)
with_running_processes(1) do
ParallelTests.wait_for_other_processes_to_finish
end
end
it "waits for other processes to finish" do
skip if RUBY_PLATFORM == "java"
ENV["TEST_ENV_NUMBER"] = "2"
counter = 0
allow(ParallelTests).to receive(:sleep) do
sleep 0.1
ParallelTests.pids.delete(1) if counter > 3
counter += 1
end
with_running_processes(2, 0.6) do
ParallelTests.wait_for_other_processes_to_finish
end
expect(counter).to be >= 2
end
end
describe ".number_of_running_processes" do
around do |example|
ParallelTests.with_pid_file do
example.run
end
end
it "is 0 for nothing" do
expect(ParallelTests.number_of_running_processes).to eq(0)
end
it "is 2 when 2 are running" do
wait = 0.2
2.times { |_x| ParallelTests.pids.add(123) }
sleep wait / 2
expect(ParallelTests.number_of_running_processes).to eq(2)
sleep wait
end
end
describe ".first_process?" do
it "is first if no env is set" do
expect(ParallelTests.first_process?).to eq(true)
end
it "is first if env is set to blank" do
ENV["TEST_ENV_NUMBER"] = ""
expect(ParallelTests.first_process?).to eq(true)
end
it "is first if env is set to 1" do
ENV["TEST_ENV_NUMBER"] = "1"
expect(ParallelTests.first_process?).to eq(true)
end
it "is not first if env is set to something else" do
ENV["TEST_ENV_NUMBER"] = "2"
expect(ParallelTests.first_process?).to eq(false)
end
end
describe ".last_process?" do
it "is last if no envs are set" do
expect(ParallelTests.last_process?).to eq(true)
end
it "is last if envs are set to blank" do
ENV["TEST_ENV_NUMBER"] = ""
ENV["PARALLEL_TEST_GROUPS"] = ""
expect(ParallelTests.last_process?).to eq(true)
end
it "is last if TEST_ENV_NUMBER is set to PARALLEL_TEST_GROUPS" do
ENV["TEST_ENV_NUMBER"] = "4"
ENV["PARALLEL_TEST_GROUPS"] = "4"
expect(ParallelTests.last_process?).to eq(true)
end
it "is not last if TEST_ENV_NUMBER is set to else" do
ENV["TEST_ENV_NUMBER"] = "2"
ENV["PARALLEL_TEST_GROUPS"] = "4"
expect(ParallelTests.first_process?).to eq(false)
end
end
describe ".stop_all_processes" do
# Process.kill on Windows doesn't work as expected. It kills all process group instead of just one process.
it 'kills the running child process', unless: Gem.win_platform? do
ParallelTests.with_pid_file do
Thread.new do
ParallelTests::Test::Runner.execute_command(['sleep', '3'], 1, 1, {})
end
sleep(0.2)
expect(ParallelTests.pids.count).to eq(1)
ParallelTests.stop_all_processes
sleep(0.2)
expect(ParallelTests.pids.count).to eq(0)
end
end
it "doesn't fail if the pid has already been killed", unless: Gem.win_platform? do
ParallelTests.with_pid_file do
ParallelTests.pids.add(1234)
expect { ParallelTests.stop_all_processes }.not_to raise_error
end
end
end
it "has a version" do
expect(ParallelTests::VERSION).to match(/^\d+\.\d+\.\d+/)
end
end
parallel_tests-5.4.0/spec/rails_spec.rb 0000664 0000000 0000000 00000003164 15043316274 0020152 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'fileutils'
require 'spec_helper'
describe 'rails' do
let(:test_timeout) { 800 } # this can take very long on fresh bundle ...
def run(command, options = {})
result = IO.popen(options.fetch(:environment, {}), command, err: [:child, :out], &:read)
raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
result
end
Dir["spec/fixtures/rails*"].each do |folder|
rails = File.basename(folder)
it "can create and run #{rails}" do
skip 'ruby 3.1 is not supported by rails 72' if RUBY_VERSION < "3.2.0" && rails == "rails72"
skip 'rails fixtures are not set up for java' if RUBY_PLATFORM == "java"
Dir.chdir("spec/fixtures/#{rails}") do
Bundler.with_unbundled_env do
ENV.delete "RUBYLIB"
run ["bundle", "config", "--local", "path", "vendor/bundle"]
run ["bundle", "config", "--local", "frozen", "true"]
run ["bundle", "install"]
FileUtils.rm_f(Dir['db/*.sqlite3'])
run ["bundle", "exec", "rake", "db:setup", "parallel:create"]
# Also test the case where the DBs need to be dropped
run ["bundle", "exec", "rake", "parallel:drop", "parallel:create"]
run ["bundle", "exec", "rake", "parallel:setup"]
run ["bundle", "exec", "rake", "parallel:prepare"]
run ["bundle", "exec", "rails", "runner", "User.create"], environment: { 'RAILS_ENV' => 'test' } # pollute the db
out = run ["bundle", "exec", "rake", "parallel:prepare", "parallel:test"]
expect(out).to match(/ 2 (tests|runs)/)
end
end
end
end
end
parallel_tests-5.4.0/spec/spec_helper.rb 0000664 0000000 0000000 00000015302 15043316274 0020314 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'bundler/setup'
require 'tempfile'
require 'tmpdir'
require 'timeout'
require 'parallel_tests'
require 'parallel_tests/test/runtime_logger'
require 'parallel_tests/rspec/runtime_logger'
require 'parallel_tests/rspec/summary_logger'
require 'parallel_tests/rspec/verbose_logger'
String.class_eval do
def strip_heredoc
gsub(/^#{self[/^\s*/]}/, '')
end
end
OutputLogger = Struct.new(:output) do
attr_reader :flock, :flush
def puts(s = nil)
output << "#{s}\n"
end
def print(s = nil)
output << s.to_s
end
end
module SpecHelper
def mocked_process
StringIO.new
end
def size_of(group)
group.map { |test| File.stat(test).size }.inject(:+)
end
def use_temporary_directory(&block)
Dir.mktmpdir { |dir| Dir.chdir(dir, &block) }
end
def with_files(files)
Dir.mktmpdir do |root|
files.each do |file|
parent = "#{root}/#{File.dirname(file)}"
FileUtils.mkpath(parent)
FileUtils.touch(File.join(root, file))
end
yield root
end
end
def should_run_with(command, *args)
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |a, _b, _c, _d|
expect(a.first(command.length)).to eq(command)
args.each do |arg|
expect(a).to include(arg)
end
end
end
def should_not_run_with(arg)
expect(ParallelTests::Test::Runner).to receive(:execute_command) do |a, _b, _c, _d|
expect(a).to_not include(arg)
end
end
end
module SharedExamples
def test_tests_in_groups(klass, suffix)
describe ".tests_in_groups" do
let(:log) { klass.runtime_log }
let(:test_root) { "temp" }
around { |test| use_temporary_directory(&test) }
before do
FileUtils.mkdir test_root
@files = [0, 1, 2, 3, 4, 5, 6, 7].map { |i| "#{test_root}/x#{i}#{suffix}" }
@files.each { |file| File.write(file, 'x' * 100) }
FileUtils.mkdir_p File.dirname(log)
end
def setup_runtime_log # rubocop:disable Lint/NestedMethodDefinition
File.open(log, 'w') do |f|
@files[1..].each { |file| f.puts "#{file}:#{@files.index(file)}" }
f.puts "#{@files[0]}:10"
end
end
it "groups when given an array of files" do
list_of_files = Dir["#{test_root}/**/*#{suffix}"]
result = list_of_files.dup
klass.send(:sort_by_filesize, result)
expect(result).to match_array(list_of_files.map { |file| [file, File.stat(file).size] })
end
it "finds all tests" do
found = klass.tests_in_groups([test_root], 1)
all = [Dir["#{test_root}/**/*#{suffix}"]]
expect(found.flatten - all.flatten).to eq([])
end
it "partitions them into groups by equal size" do
groups = klass.tests_in_groups([test_root], 2)
expect(groups.map { |g| size_of(g) }).to eq([400, 400])
end
it 'should partition correctly with a group size of 4' do
groups = klass.tests_in_groups([test_root], 4)
expect(groups.map { |g| size_of(g) }).to eq([200, 200, 200, 200])
end
it 'should partition correctly with an uneven group size' do
groups = klass.tests_in_groups([test_root], 3)
expect(groups.map { |g| size_of(g) }).to match_array([300, 300, 200])
end
it "partitions by runtime when runtime-data is available" do
allow(klass).to receive(:puts)
setup_runtime_log
groups = klass.tests_in_groups([test_root], 2)
expect(groups.size).to eq(2)
# 10 + 1 + 3 + 5 = 19
expect(groups[0]).to eq([@files[0], @files[1], @files[3], @files[5]])
# 2 + 4 + 6 + 7 = 19
expect(groups[1]).to eq([@files[2], @files[4], @files[6], @files[7]])
end
it 'partitions from custom runtime-data location' do
allow(klass).to receive(:puts)
allow_any_instance_of(klass).to receive(:runtime_log).and_return('tmp/custom_runtime.log')
setup_runtime_log
groups = klass.tests_in_groups([test_root], 2, runtime_log: log)
expect(groups.size).to eq(2)
# 10 + 1 + 3 + 5 = 19
expect(groups[0]).to eq([@files[0], @files[1], @files[3], @files[5]])
# 2 + 4 + 6 + 7 = 19
expect(groups[1]).to eq([@files[2], @files[4], @files[6], @files[7]])
end
it "alpha-sorts partitions when runtime-data is available" do
allow(klass).to receive(:puts)
setup_runtime_log
groups = klass.tests_in_groups([test_root], 2)
expect(groups.size).to eq(2)
expect(groups[0]).to eq(groups[0].sort)
expect(groups[1]).to eq(groups[1].sort)
end
it "partitions by round-robin when not sorting" do
files = ["file1.rb", "file2.rb", "file3.rb", "file4.rb"]
expect(klass).to receive(:find_tests).and_return(files)
groups = klass.tests_in_groups(files, 2, group_by: :found).sort
expect(groups[0]).to eq(["file1.rb", "file3.rb"])
expect(groups[1]).to eq(["file2.rb", "file4.rb"])
end
it "alpha-sorts partitions when not sorting by runtime" do
files = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm']
expect(klass).to receive(:find_tests).and_return(files)
groups = klass.tests_in_groups(files, 2, group_by: :found).sort
expect(groups[0]).to eq(groups[0].sort)
expect(groups[1]).to eq(groups[1].sort)
end
end
end
end
RSpec::Matchers.define :include_exactly_times do |expected, times|
match do |actual|
actual.scan(expected).size == times
end
failure_message do |actual|
action = (expected.is_a?(String) ? "to contain '#{expected}'" : "to match /#{expected}/")
outcome = (expected.is_a?(String) ? "appears" : "matches")
<<~FAILURE
expected the following string:
""""
#{actual}
""""
#{action} #{times} time(s), but it #{outcome} #{actual.scan(expected).size} time(s)
FAILURE
end
end
TestTookTooLong = Class.new(Timeout::Error)
RSpec.configure do |config|
config.filter_run focus: true
config.run_all_when_everything_filtered = true
config.include SpecHelper
config.extend SharedExamples
config.raise_errors_for_deprecations!
# sometimes stuff hangs -> do not hang everything
# NOTE: the timeout error can sometimes swallow errors, comment it out if you run into trouble
config.include(
Module.new do
def test_timeout
30
end
end
)
config.around do |example|
Timeout.timeout(test_timeout, TestTookTooLong, &example)
end
config.after do
ENV.delete "PARALLEL_TEST_GROUPS"
ENV.delete "PARALLEL_TEST_PROCESSORS"
ENV.delete "PARALLEL_TESTS_EXECUTABLE"
ENV.delete "TEST_ENV_NUMBER"
ENV.delete "RAILS_ENV"
end
end