turbo_tests 1.0.0 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
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