turbo_tests 1.0.0 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90e474a8156b2c560bca5ee96efb75dd9d124a97e7a088bcc8b92223f0dd7580
4
- data.tar.gz: 73a47005dbc152492b6ef7e7f4d3c80cfdd958d755f998db453bc1f2cf7f48a8
3
+ metadata.gz: 748a8d1ce5e4bd659cbef9af59b3f896b2c5b1940ae252a1a9c000c8a0237f39
4
+ data.tar.gz: 30dfc4c96dd9d624114fba30f628b500396da5775db3298b8aefeaca1407eb9f
5
5
  SHA512:
6
- metadata.gz: 9956bf53df8328cff65ef313397f358607ea359d23d086a07389c9fdc673169919220273cfcf77df267f72d45774524ded5318720fd23c59962fcb62d7eb0c68
7
- data.tar.gz: dcacad7897a69b0701993256fa2185454639c91c71810938e9cab574d0d1e601dacb74fe46b68930ddeaf8deba7a40fbc1a95b95b1fff055cffc894a191bf9e8
6
+ metadata.gz: 568f6fec127a34381085a8465f1929a6324a12e010a4cdb78ba3956cbf9922840b199f2472520a37f0a013dc8e78d143b80748a029d9f15999aa49d974447a56
7
+ data.tar.gz: f655769722f00cbe2e6d3bc756666c3bb0cababb646466717952f11c99c15868ff144e6bc321e488e2a57d07bb959b39e86f7dac321c0c05d67f24e943168867
@@ -0,0 +1,33 @@
1
+ # A sample workflow which checks out your Infrastructure as Code Configuration files,
2
+ # such as Kubernetes, Helm & Terraform and scans them for any security issues.
3
+ # The results are then uploaded to GitHub Security Code Scanning
4
+ #
5
+ # For more examples, including how to limit scans to only high-severity issues
6
+ # and fail PR checks, see https://github.com/snyk/actions/
7
+
8
+ name: Snyk Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ # The branches below must be a subset of the branches above
15
+ branches: [ master ]
16
+ schedule:
17
+ - cron: '0 0 * * 0'
18
+
19
+ jobs:
20
+ snyk:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Run Snyk to check configuration files for security issues
25
+ # Snyk can be used to break the build when it detects security issues.
26
+ # In this case we want to upload the issues to GitHub Code Scanning
27
+ continue-on-error: true
28
+ uses: snyk/actions/ruby@master
29
+ env:
30
+ # In order to use the Snyk Action you will need to have a Snyk API token.
31
+ # More details in https://github.com/snyk/actions#getting-your-snyk-token
32
+ # or you can signup for free at https://snyk.io/login
33
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
@@ -0,0 +1,25 @@
1
+ name: Tests
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+
9
+ strategy:
10
+ matrix:
11
+ ruby:
12
+ - 2.6
13
+ - 2.7
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@master
18
+ with:
19
+ ruby-version: ${{matrix.ruby}}
20
+ bundler-cache: true
21
+
22
+ - name: Run tests
23
+ timeout-minutes: 5
24
+ run: |
25
+ ${{matrix.env}} bundle exec turbo_tests -n4
data/.gitignore CHANGED
@@ -9,3 +9,60 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+
14
+ # Created by https://www.toptal.com/developers/gitignore/api/macos,vim
15
+ # Edit at https://www.toptal.com/developers/gitignore?templates=macos,vim
16
+
17
+ ### macOS ###
18
+ # General
19
+ .DS_Store
20
+ .AppleDouble
21
+ .LSOverride
22
+
23
+ # Icon must end with two \r
24
+ Icon
25
+
26
+
27
+ # Thumbnails
28
+ ._*
29
+
30
+ # Files that might appear in the root of a volume
31
+ .DocumentRevisions-V100
32
+ .fseventsd
33
+ .Spotlight-V100
34
+ .TemporaryItems
35
+ .Trashes
36
+ .VolumeIcon.icns
37
+ .com.apple.timemachine.donotpresent
38
+
39
+ # Directories potentially created on remote AFP share
40
+ .AppleDB
41
+ .AppleDesktop
42
+ Network Trash Folder
43
+ Temporary Items
44
+ .apdisk
45
+
46
+ ### Vim ###
47
+ # Swap
48
+ [._]*.s[a-v][a-z]
49
+ !*.svg # comment out if you don't need vector files
50
+ [._]*.sw[a-p]
51
+ [._]s[a-rt-v][a-z]
52
+ [._]ss[a-gi-z]
53
+ [._]sw[a-p]
54
+
55
+ # Session
56
+ Session.vim
57
+ Sessionx.vim
58
+
59
+ # Temporary
60
+ .netrwhist
61
+ *~
62
+ # Auto-generated tag files
63
+ tags
64
+ # Persistent undo
65
+ [._]*.un~
66
+
67
+ # End of https://www.toptal.com/developers/gitignore/api/macos,vim
68
+
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
+ --tty
3
4
  --require spec_helper
data/Gemfile CHANGED
@@ -3,4 +3,4 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in turbo_tests.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake", "~> 12.0"
6
+ gem "rake", "~> 13.0"
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- turbo_tests (0.1.0)
5
- optparse
6
- parallel_tests
7
- rspec
4
+ turbo_tests (1.2.4)
5
+ bundler
6
+ parallel_tests (~> 3.3)
7
+ rspec (~> 3.10.0)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
@@ -12,34 +12,33 @@ GEM
12
12
  coderay (1.1.3)
13
13
  diff-lcs (1.4.4)
14
14
  method_source (1.0.0)
15
- optparse (0.1.0)
16
- parallel (1.19.2)
17
- parallel_tests (3.3.0)
15
+ parallel (1.20.1)
16
+ parallel_tests (3.5.2)
18
17
  parallel
19
- pry (0.13.1)
18
+ pry (0.14.0)
20
19
  coderay (~> 1.1)
21
20
  method_source (~> 1.0)
22
- rake (12.3.3)
23
- rspec (3.9.0)
24
- rspec-core (~> 3.9.0)
25
- rspec-expectations (~> 3.9.0)
26
- rspec-mocks (~> 3.9.0)
27
- rspec-core (3.9.3)
28
- rspec-support (~> 3.9.3)
29
- rspec-expectations (3.9.3)
21
+ rake (13.0.3)
22
+ rspec (3.10.0)
23
+ rspec-core (~> 3.10.0)
24
+ rspec-expectations (~> 3.10.0)
25
+ rspec-mocks (~> 3.10.0)
26
+ rspec-core (3.10.1)
27
+ rspec-support (~> 3.10.0)
28
+ rspec-expectations (3.10.1)
30
29
  diff-lcs (>= 1.2.0, < 2.0)
31
- rspec-support (~> 3.9.0)
32
- rspec-mocks (3.9.1)
30
+ rspec-support (~> 3.10.0)
31
+ rspec-mocks (3.10.2)
33
32
  diff-lcs (>= 1.2.0, < 2.0)
34
- rspec-support (~> 3.9.0)
35
- rspec-support (3.9.4)
33
+ rspec-support (~> 3.10.0)
34
+ rspec-support (3.10.2)
36
35
 
37
36
  PLATFORMS
38
37
  ruby
39
38
 
40
39
  DEPENDENCIES
41
- pry
42
- rake (~> 12.0)
40
+ pry (~> 0.14)
41
+ rake (~> 13.0)
43
42
  turbo_tests!
44
43
 
45
44
  BUNDLED WITH
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ ![Tests](https://github.com/serpapi/turbo_tests/workflows/Tests/badge.svg)
2
+
1
3
  # TurboTests
2
4
 
3
5
  Runner for [`grosser/parallel_tests`](https://github.com/grosser/parallel_tests) with incremental summarized output. Based on [Discourse](https://github.com/discourse/discourse/blob/6b9784cf8a18636bce281a7e4d18e65a0cbc6290/lib/turbo_tests.rb) and [RubyGems](https://github.com/rubygems/rubygems/tree/390335ceb351668cd433bd5bb9823dd021f82533/bundler/tool) work in this area.
@@ -11,23 +13,17 @@ This feature [doesn't fit vision of `parallel_tests` author](https://github.com/
11
13
  ```bash
12
14
 
13
15
  $ bundle exec rake parallel_tests:spec[^spec/search]
14
- ...........................................................................................................................................................................................
15
- ...........................................................................................................................................................................................
16
- ...........................................................................................................................................................................................
16
+ .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
17
17
 
18
18
  Finished in 1 minute 6.92 seconds (files took 6.95 seconds to load)
19
19
  2616 examples, 0 failures
20
20
 
21
- .........................................................................................................................................F.................................................
22
- .......................................................................................F...................................................................................................
23
- ...........................................................................................................................................................................................
21
+ .........................................................................................................................................F........................................................................................................................................F..............................................................................................................................................................................................................................................................................................
24
22
 
25
23
  Finished in 1 minute 35.05 seconds (files took 6.26 seconds to load)
26
24
  2158 examples, 2 failures
27
25
 
28
- ...........................................................................................................................................................................................
29
- ...........................................................................................................................................................................................
30
- ...........................................................................................................................................................................................
26
+ .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
31
27
 
32
28
  Finished in 1 minute 35.05 seconds (files took 6.26 seconds to load)
33
29
  2158 examples, 0 failures
@@ -36,16 +32,8 @@ Finished in 1 minute 35.05 seconds (files took 6.26 seconds to load)
36
32
  `turbo_tests` output looks like regular `rspec`:
37
33
 
38
34
  ```bash
39
- $ bundle exec turbo_spec
40
- ...........................................................................................................................................................................................
41
- ...........................................................................................................................................................................................
42
- ...........................................................................................................................................................................................
43
- .........................................................................................................................................F.................................................
44
- .......................................................................................F...................................................................................................
45
- ...........................................................................................................................................................................................
46
- ...........................................................................................................................................................................................
47
- ...........................................................................................................................................................................................
48
- ...........................................................................................................................................................................................
35
+ $ bundle exec turbo_tests
36
+ ..........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................F........................................................................................................................................F..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
49
37
 
50
38
  Finished in 2 minute 25.15 seconds (files took 0 seconds to load)
51
39
  6873 examples, 2 failures
@@ -80,17 +68,20 @@ Show help:
80
68
  ```bash
81
69
  $ bundle exec turbo_tests -h
82
70
  Usage: turbo_tests [options]
71
+
72
+ [optional] Only selected files & folders:
73
+ turbo_tests spec/bar spec/baz/xxx_spec.rb
74
+
75
+ Options:
76
+ -n [PROCESSES] How many processes to use, default: available CPUs
83
77
  -r, --require PATH Require a file.
84
- -f, --format FORMATTER Choose a formatter.
78
+ -f, --format FORMATTER Choose a formatter. Available formatters: progress (p), documentation (d). Default: progress
85
79
  -t, --tag TAG Run examples with the specified tag.
86
80
  -o, --out FILE Write output to a file instead of $stdout
87
81
  -v, --verbose More output
88
82
  --fail-fast=[N]
89
83
  ```
90
84
 
91
- ## Roadmap
92
- - [ ] Capture time to load files #1
93
-
94
85
  ## Development
95
86
 
96
87
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require "bundler/setup"
1
2
  require "bundler/gem_tasks"
2
3
  require "rspec/core/rake_task"
3
4
 
data/bin/turbo_tests ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ # Enable local usage from cloned repo
6
+ root = File.expand_path("../..", __FILE__)
7
+ $LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
8
+
9
+ require "turbo_tests"
10
+
11
+ TurboTests::CLI.new(ARGV).run
@@ -0,0 +1,5 @@
1
+ RSpec.describe "Fixture of spec file with errors outside of examples" do
2
+ it("passes") { expect(2 * 2).to eql(4) }
3
+
4
+ 1 / 0
5
+ end
data/lib/turbo_tests.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "turbo_tests/version"
4
- require "bundler/setup"
5
-
6
3
  require "open3"
7
4
  require "fileutils"
8
5
  require "json"
@@ -16,6 +13,9 @@ require "turbo_tests/runner"
16
13
  require "turbo_tests/json_rows_formatter"
17
14
 
18
15
  module TurboTests
16
+ autoload :CLI, "turbo_tests/cli"
17
+ autoload :VERSION, "turbo_tests/version"
18
+
19
19
  FakeException = Struct.new(:backtrace, :message, :cause)
20
20
  class FakeException
21
21
  def self.from_obj(obj)
@@ -23,14 +23,14 @@ module TurboTests
23
23
  klass =
24
24
  Class.new(FakeException) {
25
25
  define_singleton_method(:name) do
26
- obj["class_name"]
26
+ obj[:class_name]
27
27
  end
28
28
  }
29
29
 
30
30
  klass.new(
31
- obj["backtrace"],
32
- obj["message"],
33
- FakeException.from_obj(obj["cause"])
31
+ obj[:backtrace],
32
+ obj[:message],
33
+ FakeException.from_obj(obj[:cause])
34
34
  )
35
35
  end
36
36
  end
@@ -40,11 +40,11 @@ module TurboTests
40
40
  class FakeExecutionResult
41
41
  def self.from_obj(obj)
42
42
  new(
43
- obj["example_skipped?"],
44
- obj["pending_message"],
45
- obj["status"].to_sym,
46
- obj["pending_fixed?"],
47
- FakeException.from_obj(obj["exception"])
43
+ obj[:example_skipped?],
44
+ obj[:pending_message],
45
+ obj[:status].to_sym,
46
+ obj[:pending_fixed?],
47
+ FakeException.from_obj(obj[:exception])
48
48
  )
49
49
  end
50
50
  end
@@ -52,24 +52,24 @@ module TurboTests
52
52
  FakeExample = Struct.new(:execution_result, :location, :description, :full_description, :metadata, :location_rerun_argument)
53
53
  class FakeExample
54
54
  def self.from_obj(obj)
55
- metadata = obj["metadata"]
55
+ metadata = obj[:metadata]
56
56
 
57
- metadata["shared_group_inclusion_backtrace"].map! do |frame|
57
+ metadata[:shared_group_inclusion_backtrace].map! do |frame|
58
58
  RSpec::Core::SharedExampleGroupInclusionStackFrame.new(
59
- frame["shared_group_name"],
60
- frame["inclusion_location"]
59
+ frame[:shared_group_name],
60
+ frame[:inclusion_location]
61
61
  )
62
62
  end
63
63
 
64
- metadata[:shared_group_inclusion_backtrace] = metadata.delete("shared_group_inclusion_backtrace")
64
+ metadata[:shared_group_inclusion_backtrace] = metadata.delete(:shared_group_inclusion_backtrace)
65
65
 
66
66
  new(
67
- FakeExecutionResult.from_obj(obj["execution_result"]),
68
- obj["location"],
69
- obj["description"],
70
- obj["full_description"],
67
+ FakeExecutionResult.from_obj(obj[:execution_result]),
68
+ obj[:location],
69
+ obj[:description],
70
+ obj[:full_description],
71
71
  metadata,
72
- obj["location_rerun_argument"]
72
+ obj[:location_rerun_argument]
73
73
  )
74
74
  end
75
75
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "turbo_tests"
4
3
  require "optparse"
5
4
 
6
5
  module TurboTests
@@ -13,15 +12,31 @@ module TurboTests
13
12
  requires = []
14
13
  formatters = []
15
14
  tags = []
15
+ count = nil
16
16
  verbose = false
17
17
  fail_fast = nil
18
18
 
19
19
  OptionParser.new { |opts|
20
+ opts.banner = <<~BANNER
21
+ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('1', '2', '3', ...).
22
+
23
+ Uses parallel_tests under the hood, but reports test results incrementally. Based on Discourse and RubyGems work in this area.
24
+
25
+ Usage: turbo_tests [options]
26
+
27
+ [optional] Only selected files & folders:
28
+ turbo_tests spec/bar spec/baz/xxx_spec.rb
29
+
30
+ Options:
31
+ BANNER
32
+
33
+ opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| count = n }
34
+
20
35
  opts.on("-r", "--require PATH", "Require a file.") do |filename|
21
36
  requires << filename
22
37
  end
23
38
 
24
- opts.on("-f", "--format FORMATTER", "Choose a formatter.") do |name|
39
+ opts.on("-f", "--format FORMATTER", "Choose a formatter. Available formatters: progress (p), documentation (d). Default: progress") do |name|
25
40
  formatters << {
26
41
  name: name,
27
42
  outputs: []
@@ -71,13 +86,20 @@ module TurboTests
71
86
  end
72
87
  end
73
88
 
74
- exit TurboTests::Runner.run(
89
+ success = TurboTests::Runner.run(
75
90
  formatters: formatters,
76
91
  tags: tags,
77
92
  files: @argv.empty? ? ["spec"] : @argv,
78
93
  verbose: verbose,
79
- fail_fast: fail_fast
94
+ fail_fast: fail_fast,
95
+ count: count,
80
96
  )
97
+
98
+ if success
99
+ exit 0
100
+ else
101
+ exit 1
102
+ end
81
103
  end
82
104
  end
83
105
  end
@@ -22,10 +22,15 @@ module TurboTests
22
22
  class JsonRowsFormatter
23
23
  RSpec::Core::Formatters.register(
24
24
  self,
25
+ :start,
25
26
  :close,
26
27
  :example_failed,
27
28
  :example_passed,
28
29
  :example_pending,
30
+ :example_group_started,
31
+ :example_group_finished,
32
+ :example_pending,
33
+ :message,
29
34
  :seed
30
35
  )
31
36
 
@@ -35,37 +40,65 @@ module TurboTests
35
40
  @output = output
36
41
  end
37
42
 
43
+ def start(notification)
44
+ output_row(
45
+ type: :load_summary,
46
+ summary: load_summary_to_json(notification)
47
+ )
48
+ end
49
+
50
+ def example_group_started(notification)
51
+ output_row(
52
+ type: :group_started,
53
+ group: group_to_json(notification)
54
+ )
55
+ end
56
+
57
+ def example_group_finished(notification)
58
+ output_row(
59
+ type: :group_finished,
60
+ group: group_to_json(notification)
61
+ )
62
+ end
63
+
38
64
  def example_passed(notification)
39
65
  output_row(
40
- "type" => :example_passed,
41
- "example" => example_to_json(notification.example)
66
+ type: :example_passed,
67
+ example: example_to_json(notification.example)
42
68
  )
43
69
  end
44
70
 
45
71
  def example_pending(notification)
46
72
  output_row(
47
- "type" => :example_pending,
48
- "example" => example_to_json(notification.example)
73
+ type: :example_pending,
74
+ example: example_to_json(notification.example)
49
75
  )
50
76
  end
51
77
 
52
78
  def example_failed(notification)
53
79
  output_row(
54
- "type" => :example_failed,
55
- "example" => example_to_json(notification.example)
80
+ type: :example_failed,
81
+ example: example_to_json(notification.example)
56
82
  )
57
83
  end
58
84
 
59
85
  def seed(notification)
60
86
  output_row(
61
- "type" => :seed,
62
- "seed" => notification.seed
87
+ type: :seed,
88
+ seed: notification.seed
63
89
  )
64
90
  end
65
91
 
66
92
  def close(notification)
67
93
  output_row(
68
- "type" => :close
94
+ type: :close
95
+ )
96
+ end
97
+
98
+ def message(notification)
99
+ output_row(
100
+ type: :message,
101
+ message: notification.message
69
102
  )
70
103
  end
71
104
 
@@ -74,47 +107,65 @@ module TurboTests
74
107
  def exception_to_json(exception)
75
108
  if exception
76
109
  {
77
- "class_name" => exception.class.name.to_s,
78
- "backtrace" => exception.backtrace,
79
- "message" => exception.message,
80
- "cause" => exception_to_json(exception.cause)
110
+ class_name: exception.class.name.to_s,
111
+ backtrace: exception.backtrace,
112
+ message: exception.message,
113
+ cause: exception_to_json(exception.cause)
81
114
  }
82
115
  end
83
116
  end
84
117
 
85
118
  def execution_result_to_json(result)
86
119
  {
87
- "example_skipped?" => result.example_skipped?,
88
- "pending_message" => result.pending_message,
89
- "status" => result.status,
90
- "pending_fixed?" => result.pending_fixed?,
91
- "exception" => exception_to_json(result.exception)
120
+ example_skipped?: result.example_skipped?,
121
+ pending_message: result.pending_message,
122
+ status: result.status,
123
+ pending_fixed?: result.pending_fixed?,
124
+ exception: exception_to_json(result.exception)
92
125
  }
93
126
  end
94
127
 
95
128
  def stack_frame_to_json(frame)
96
129
  {
97
- "shared_group_name" => frame.shared_group_name,
98
- "inclusion_location" => frame.inclusion_location
130
+ shared_group_name: frame.shared_group_name,
131
+ inclusion_location: frame.inclusion_location
99
132
  }
100
133
  end
101
134
 
102
135
  def example_to_json(example)
103
136
  {
104
- "execution_result" => execution_result_to_json(example.execution_result),
105
- "location" => example.location,
106
- "description" => example.description,
107
- "full_description" => example.full_description,
108
- "metadata" => {
109
- "shared_group_inclusion_backtrace" =>
110
- example.metadata[:shared_group_inclusion_backtrace].map { |frame| stack_frame_to_json(frame) }
137
+ execution_result: execution_result_to_json(example.execution_result),
138
+ location: example.location,
139
+ description: example.description,
140
+ full_description: example.full_description,
141
+ metadata: {
142
+ shared_group_inclusion_backtrace:
143
+ example
144
+ .metadata[:shared_group_inclusion_backtrace]
145
+ .map { |frame| stack_frame_to_json(frame) }
111
146
  },
112
- "location_rerun_argument" => example.location_rerun_argument
147
+ location_rerun_argument: example.location_rerun_argument
148
+ }
149
+ end
150
+
151
+ def load_summary_to_json(notification)
152
+ {
153
+ count: notification.count,
154
+ load_time: notification.load_time,
155
+ }
156
+ end
157
+
158
+ def group_to_json(notification)
159
+ {
160
+ group: {
161
+ description: notification.group.description
162
+ }
113
163
  }
114
164
  end
115
165
 
116
166
  def output_row(obj)
117
- output.puts ENV["RSPEC_FORMATTER_OUTPUT_ID"] + obj.to_json
167
+ output.puts(obj.to_json)
168
+ output.flush
118
169
  end
119
170
  end
120
171
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module TurboTests
4
4
  class Reporter
5
+ attr_writer :load_time
6
+
5
7
  def self.from_config(formatter_config, start_time)
6
8
  reporter = new(start_time)
7
9
 
@@ -26,7 +28,10 @@ module TurboTests
26
28
  @pending_examples = []
27
29
  @failed_examples = []
28
30
  @all_examples = []
31
+ @messages = []
29
32
  @start_time = start_time
33
+ @load_time = 0
34
+ @errors_outside_of_examples_count = 0
30
35
  end
31
36
 
32
37
  def add(name, outputs)
@@ -45,6 +50,14 @@ module TurboTests
45
50
  end
46
51
  end
47
52
 
53
+ def group_started(notification)
54
+ delegate_to_formatters(:example_group_started, notification)
55
+ end
56
+
57
+ def group_finished
58
+ delegate_to_formatters(:example_group_finished, nil)
59
+ end
60
+
48
61
  def example_passed(example)
49
62
  delegate_to_formatters(:example_passed, example.notification)
50
63
 
@@ -65,8 +78,18 @@ module TurboTests
65
78
  @failed_examples << example
66
79
  end
67
80
 
81
+ def message(message)
82
+ delegate_to_formatters(:message, RSpec::Core::Notifications::MessageNotification.new(message))
83
+ @messages << message
84
+ end
85
+
86
+ def error_outside_of_examples
87
+ @errors_outside_of_examples_count += 1
88
+ end
89
+
68
90
  def finish
69
- end_time = Time.now
91
+ # SEE: https://bit.ly/2NP87Cz
92
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
70
93
 
71
94
  delegate_to_formatters(:start_dump,
72
95
  RSpec::Core::Notifications::NullNotification)
@@ -84,8 +107,8 @@ module TurboTests
84
107
  @all_examples,
85
108
  @failed_examples,
86
109
  @pending_examples,
87
- 0,
88
- 0
110
+ @load_time,
111
+ @errors_outside_of_examples_count
89
112
  ))
90
113
  delegate_to_formatters(:close,
91
114
  RSpec::Core::Notifications::NullNotification)
@@ -1,18 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
-
5
4
  require "parallel_tests/rspec/runner"
6
5
 
6
+ require_relative "../utils/hash_extension"
7
+
7
8
  module TurboTests
8
9
  class Runner
10
+ using CoreExtensions
11
+
9
12
  def self.run(opts = {})
10
13
  files = opts[:files]
11
14
  formatters = opts[:formatters]
12
15
  tags = opts[:tags]
13
- start_time = opts.fetch(:start_time) { Time.now }
16
+
17
+ # SEE: https://bit.ly/2NP87Cz
18
+ start_time = opts.fetch(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
14
19
  verbose = opts.fetch(:verbose, false)
15
20
  fail_fast = opts.fetch(:fail_fast, nil)
21
+ count = opts.fetch(:count, nil)
22
+
23
+ if verbose
24
+ STDERR.puts "VERBOSE"
25
+ end
16
26
 
17
27
  reporter = Reporter.from_config(formatters, start_time)
18
28
 
@@ -21,7 +31,8 @@ module TurboTests
21
31
  files: files,
22
32
  tags: tags,
23
33
  verbose: verbose,
24
- fail_fast: fail_fast
34
+ fail_fast: fail_fast,
35
+ count: count
25
36
  ).run
26
37
  end
27
38
 
@@ -31,25 +42,49 @@ module TurboTests
31
42
  @tags = opts[:tags]
32
43
  @verbose = opts[:verbose]
33
44
  @fail_fast = opts[:fail_fast]
45
+ @count = opts[:count]
46
+ @load_time = 0
47
+ @load_count = 0
34
48
  @failure_count = 0
35
- @runtime_log = "tmp/parallel_runtime_rspec.log"
36
49
 
37
50
  @messages = Queue.new
38
51
  @threads = []
52
+ @error = false
39
53
  end
40
54
 
41
55
  def run
42
- @num_processes = ParallelTests.determine_number_of_processes(nil)
56
+ @num_processes = [
57
+ ParallelTests.determine_number_of_processes(@count),
58
+ ParallelTests::RSpec::Runner.tests_with_size(@files, {}).size
59
+ ].min
60
+
61
+ use_runtime_info = @files == ["spec"]
62
+
63
+ group_opts = {}
64
+
65
+ if use_runtime_info
66
+ group_opts[:runtime_log] = "tmp/turbo_rspec_runtime.log"
67
+ else
68
+ group_opts[:group_by] = :filesize
69
+ end
43
70
 
44
71
  tests_in_groups =
45
72
  ParallelTests::RSpec::Runner.tests_in_groups(
46
73
  @files,
47
74
  @num_processes,
48
- runtime_log: @runtime_log
75
+ **group_opts
49
76
  )
50
77
 
51
- tests_in_groups.each_with_index do |tests, process_id|
52
- start_regular_subprocess(tests, process_id + 1)
78
+ setup_tmp_dir
79
+
80
+ subprocess_opts = {
81
+ record_runtime: use_runtime_info
82
+ }
83
+
84
+ report_number_of_tests(tests_in_groups)
85
+
86
+ wait_threads = tests_in_groups.map.with_index do |tests, process_id|
87
+ start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
53
88
  end
54
89
 
55
90
  handle_messages
@@ -58,37 +93,64 @@ module TurboTests
58
93
 
59
94
  @threads.each(&:join)
60
95
 
61
- @reporter.failed_examples.empty?
96
+ @reporter.failed_examples.empty? && wait_threads.map(&:value).all?(&:success?)
62
97
  end
63
98
 
64
- protected
99
+ private
100
+
101
+ def setup_tmp_dir
102
+ begin
103
+ FileUtils.rm_r("tmp/test-pipes")
104
+ rescue Errno::ENOENT
105
+ end
106
+
107
+ FileUtils.mkdir_p("tmp/test-pipes/")
108
+ end
65
109
 
66
- def start_regular_subprocess(tests, process_id)
110
+ def start_regular_subprocess(tests, process_id, **opts)
67
111
  start_subprocess(
68
112
  {"TEST_ENV_NUMBER" => process_id.to_s},
69
113
  @tags.map { |tag| "--tag=#{tag}" },
70
114
  tests,
71
- process_id
115
+ process_id,
116
+ **opts
72
117
  )
73
118
  end
74
119
 
75
- def start_subprocess(env, extra_args, tests, process_id)
120
+ def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
76
121
  if tests.empty?
77
122
  @messages << {
78
- "type" => "exit",
79
- "process_id" => process_id
123
+ type: "exit",
124
+ process_id: process_id
80
125
  }
81
126
  else
82
- require "securerandom"
83
- env["RSPEC_FORMATTER_OUTPUT_ID"] = SecureRandom.uuid
84
- env["RUBYOPT"] = "-I#{File.expand_path("..", __dir__)}"
127
+ tmp_filename = "tmp/test-pipes/subprocess-#{process_id}"
128
+
129
+ begin
130
+ File.mkfifo(tmp_filename)
131
+ rescue Errno::EEXIST
132
+ end
133
+
134
+ env["RUBYOPT"] = ["-I#{File.expand_path("..", __dir__)}", ENV["RUBYOPT"]].compact.join(" ")
135
+ env["RSPEC_SILENCE_FILTER_ANNOUNCEMENTS"] = "1"
136
+
137
+ record_runtime_options =
138
+ if record_runtime
139
+ [
140
+ "--format", "ParallelTests::RSpec::RuntimeLogger",
141
+ "--out", "tmp/turbo_rspec_runtime.log",
142
+ ]
143
+ else
144
+ []
145
+ end
85
146
 
86
147
  command = [
87
- "bundle", "exec", "rspec",
148
+ ENV["BUNDLE_BIN_PATH"], "exec", "rspec",
88
149
  *extra_args,
89
- "--format", "ParallelTests::RSpec::RuntimeLogger",
90
- "--out", @runtime_log,
150
+ "--seed", rand(0xFFFF).to_s,
91
151
  "--format", "TurboTests::JsonRowsFormatter",
152
+ "--out", tmp_filename,
153
+ *record_runtime_options,
92
154
  *tests
93
155
  ]
94
156
 
@@ -101,29 +163,33 @@ module TurboTests
101
163
  STDERR.puts "Process #{process_id}: #{command_str}"
102
164
  end
103
165
 
104
- _stdin, stdout, stderr, _wait_thr = Open3.popen3(env, *command)
166
+ stdin, stdout, stderr, wait_thr = Open3.popen3(env, *command)
167
+ stdin.close
105
168
 
106
169
  @threads <<
107
- Thread.new {
108
- require "json"
109
- stdout.each_line do |line|
110
- result = line.split(env["RSPEC_FORMATTER_OUTPUT_ID"])
170
+ Thread.new do
171
+ File.open(tmp_filename) do |fd|
172
+ fd.each_line do |line|
173
+ message = JSON.parse(line, symbolize_names: true)
111
174
 
112
- output = result.shift
113
- STDOUT.print(output) unless output.empty?
114
-
115
- message = result.shift
116
- next unless message
117
-
118
- message = JSON.parse(message)
119
- message["process_id"] = process_id
120
- @messages << message
175
+ message[:process_id] = process_id
176
+ @messages << message
177
+ end
121
178
  end
122
179
 
123
- @messages << {"type" => "exit", "process_id" => process_id}
124
- }
180
+ @messages << {type: "exit", process_id: process_id}
181
+ end
125
182
 
183
+ @threads << start_copy_thread(stdout, STDOUT)
126
184
  @threads << start_copy_thread(stderr, STDERR)
185
+
186
+ @threads << Thread.new {
187
+ unless wait_thr.value.success?
188
+ @messages << {type: "error"}
189
+ end
190
+ }
191
+
192
+ wait_thr
127
193
  end
128
194
  end
129
195
 
@@ -132,6 +198,7 @@ module TurboTests
132
198
  loop do
133
199
  msg = src.readpartial(4096)
134
200
  rescue EOFError
201
+ src.close
135
202
  break
136
203
  else
137
204
  dst.write(msg)
@@ -144,31 +211,47 @@ module TurboTests
144
211
 
145
212
  loop do
146
213
  message = @messages.pop
147
- case message["type"]
214
+ case message[:type]
148
215
  when "example_passed"
149
- example = FakeExample.from_obj(message["example"])
216
+ example = FakeExample.from_obj(message[:example])
150
217
  @reporter.example_passed(example)
218
+ when "group_started"
219
+ @reporter.group_started(message[:group].to_struct)
220
+ when "group_finished"
221
+ @reporter.group_finished
151
222
  when "example_pending"
152
- example = FakeExample.from_obj(message["example"])
223
+ example = FakeExample.from_obj(message[:example])
153
224
  @reporter.example_pending(example)
225
+ when "load_summary"
226
+ message = message[:summary]
227
+ # NOTE: notifications order and content is not guaranteed hence the fetch
228
+ # and count increment tracking to get the latest accumulated load time
229
+ @reporter.load_time = message[:load_time] if message.fetch(:count, 0) > @load_count
154
230
  when "example_failed"
155
- example = FakeExample.from_obj(message["example"])
231
+ example = FakeExample.from_obj(message[:example])
156
232
  @reporter.example_failed(example)
157
233
  @failure_count += 1
158
234
  if fail_fast_met
159
235
  @threads.each(&:kill)
160
236
  break
161
237
  end
238
+ when "message"
239
+ @reporter.message(message[:message])
162
240
  when "seed"
163
241
  when "close"
242
+ when "error"
243
+ @reporter.error_outside_of_examples
244
+ @error = true
164
245
  when "exit"
165
246
  exited += 1
166
247
  if exited == @num_processes
167
248
  break
168
249
  end
169
250
  else
170
- warn("Unhandled message in main process: #{message}")
251
+ STDERR.puts("Unhandled message in main process: #{message}")
171
252
  end
253
+
254
+ STDOUT.flush
172
255
  end
173
256
  rescue Interrupt
174
257
  end
@@ -176,5 +259,15 @@ module TurboTests
176
259
  def fail_fast_met
177
260
  !@fail_fast.nil? && @fail_fast >= @failure_count
178
261
  end
262
+
263
+ def report_number_of_tests(groups)
264
+ name = ParallelTests::RSpec::Runner.test_file_name
265
+
266
+ num_processes = groups.size
267
+ num_tests = groups.map(&:size).sum
268
+ tests_per_process = (num_processes == 0 ? 0 : num_tests.to_f / num_processes).round
269
+
270
+ puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{tests_per_process} #{name}s per process"
271
+ end
179
272
  end
180
273
  end
@@ -1,3 +1,3 @@
1
1
  module TurboTests
2
- VERSION = "1.0.0"
2
+ VERSION = "1.2.4"
3
3
  end
@@ -0,0 +1,9 @@
1
+ module CoreExtensions
2
+ refine Hash do
3
+ def to_struct
4
+ OpenStruct.new(self.each_with_object({}) do |(key, val), acc|
5
+ acc[key] = val.is_a?(Hash) ? val.to_struct : val
6
+ end)
7
+ end
8
+ end
9
+ end
data/turbo_tests.gemspec CHANGED
@@ -4,22 +4,24 @@ Gem::Specification.new do |spec|
4
4
  spec.name = "turbo_tests"
5
5
  spec.version = TurboTests::VERSION
6
6
  spec.authors = ["Ilya Zub"]
7
- spec.email = ["ilya@serpapi.com"]
7
+ spec.email = ["zaoooza92@gmail.com"]
8
8
 
9
9
  spec.summary = "Runner for grosser/parallel_tests with incremental summarized output. Based on Discourse and Rubygems work in this area."
10
10
  spec.homepage = "https://github.com/serpapi/turbo_tests"
11
11
  spec.license = "MIT"
12
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
12
 
14
13
  spec.metadata["homepage_uri"] = spec.homepage
15
14
  spec.metadata["source_code_uri"] = "https://github.com/serpapi/turbo_tests"
16
15
  spec.metadata["changelog_uri"] = "https://github.com/serpapi/turbo_tests/releases"
17
16
 
18
- spec.add_dependency "rspec"
19
- spec.add_dependency "parallel_tests"
20
- spec.add_dependency "optparse"
17
+ spec.required_ruby_version = ">= 2.4.0"
21
18
 
22
- spec.add_development_dependency "pry"
19
+ spec.add_dependency "rspec", "~> 3.10.0"
20
+ spec.add_dependency "parallel_tests", "~> 3.3"
21
+
22
+ spec.add_development_dependency "pry", "~> 0.14"
23
+
24
+ spec.add_runtime_dependency "bundler"
23
25
 
24
26
  # Specify which files should be added to the gem when it is released.
25
27
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -27,7 +29,5 @@ Gem::Specification.new do |spec|
27
29
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
30
  end
29
31
 
30
- spec.bindir = "exe"
31
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
- spec.require_paths = ["lib"]
32
+ spec.executables = ["turbo_tests"]
33
33
  end
metadata CHANGED
@@ -1,65 +1,65 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Zub
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-30 00:00:00.000000000 Z
11
+ date: 2021-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 3.10.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 3.10.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: parallel_tests
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '3.3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '3.3'
41
41
  - !ruby/object:Gem::Dependency
42
- name: optparse
42
+ name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
47
+ version: '0.14'
48
+ type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '0.14'
55
55
  - !ruby/object:Gem::Dependency
56
- name: pry
56
+ name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- type: :development
62
+ type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
@@ -68,12 +68,14 @@ dependencies:
68
68
  version: '0'
69
69
  description:
70
70
  email:
71
- - ilya@serpapi.com
71
+ - zaoooza92@gmail.com
72
72
  executables:
73
73
  - turbo_tests
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - ".github/workflows/snyk_ruby-analysis.yml"
78
+ - ".github/workflows/tests.yml"
77
79
  - ".gitignore"
78
80
  - ".rspec"
79
81
  - ".travis.yml"
@@ -83,15 +85,15 @@ files:
83
85
  - LICENSE.txt
84
86
  - README.md
85
87
  - Rakefile
86
- - bin/console
87
- - bin/setup
88
- - exe/turbo_tests
88
+ - bin/turbo_tests
89
+ - fixtures/rspec/errors_outside_of_examples_spec.rb
89
90
  - lib/turbo_tests.rb
90
91
  - lib/turbo_tests/cli.rb
91
92
  - lib/turbo_tests/json_rows_formatter.rb
92
93
  - lib/turbo_tests/reporter.rb
93
94
  - lib/turbo_tests/runner.rb
94
95
  - lib/turbo_tests/version.rb
96
+ - lib/utils/hash_extension.rb
95
97
  - turbo_tests.gemspec
96
98
  homepage: https://github.com/serpapi/turbo_tests
97
99
  licenses:
@@ -108,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
110
  requirements:
109
111
  - - ">="
110
112
  - !ruby/object:Gem::Version
111
- version: 2.3.0
113
+ version: 2.4.0
112
114
  required_rubygems_version: !ruby/object:Gem::Requirement
113
115
  requirements:
114
116
  - - ">="
data/bin/console DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "turbo_tests"
5
-
6
- require "pry"
7
- Pry.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/exe/turbo_tests DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # frozen_string_literal: true
4
-
5
- require "turbo_tests/cli"
6
-
7
- TurboTests::CLI.new(ARGV).run