turbo_tests 1.2.0 → 1.2.5

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: ae7178127f711a2ea749e60ff16ed6e3f46011737c02bf1fd2912b6777869b0a
4
- data.tar.gz: d07339025c120451ca4e30fd13da954ded64234081759c0b0e9562954447462a
3
+ metadata.gz: b8a0dc37755b687ea503d265d864eb959d2bda3668e3f21c4b09af48e6fb852a
4
+ data.tar.gz: 8399b27819ae06f123b0695754fe074cb8819d385cd2a4d932c12b9f30a88124
5
5
  SHA512:
6
- metadata.gz: cbb71d599830fa9e86287f389aca4c678794b2258d287dab88bd975f7648bbb4e78b9986c24274b231730d069c88df0d3ce81ce46797723ae44248dec056355f
7
- data.tar.gz: 12df6012daff0f3925aab5204247174d07a6d41d85b6c3a3a79f6f9a79cdc1a7d3c0b4e874cdf405024913d856683539e31257ab4863883f57af77acbda23154
6
+ metadata.gz: cae6d818935454f40984af484fa8d87763732b578a4bb901679434c40a0d5dc152c671726523877247375c4572849c907e4190faab6e0681abb3b2d8db50e1bd
7
+ data.tar.gz: 41a44281ddb35a3440eaa633bafecd7e9bb585acf132e69180a521e68148a9feb10121ac1a7bd38c38892d56c0ba326215bacd590380355ece8b02715667697b
@@ -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 }}
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 (1.2.0)
4
+ turbo_tests (1.2.5)
5
5
  bundler
6
6
  parallel_tests (~> 3.3)
7
- rspec (~> 3.10.0)
7
+ rspec (~> 3.10)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
@@ -13,32 +13,32 @@ GEM
13
13
  diff-lcs (1.4.4)
14
14
  method_source (1.0.0)
15
15
  parallel (1.20.1)
16
- parallel_tests (3.4.0)
16
+ parallel_tests (3.7.0)
17
17
  parallel
18
- pry (0.13.1)
18
+ pry (0.14.1)
19
19
  coderay (~> 1.1)
20
20
  method_source (~> 1.0)
21
- rake (12.3.3)
21
+ rake (13.0.6)
22
22
  rspec (3.10.0)
23
23
  rspec-core (~> 3.10.0)
24
24
  rspec-expectations (~> 3.10.0)
25
25
  rspec-mocks (~> 3.10.0)
26
- rspec-core (3.10.0)
26
+ rspec-core (3.10.1)
27
27
  rspec-support (~> 3.10.0)
28
- rspec-expectations (3.10.0)
28
+ rspec-expectations (3.10.1)
29
29
  diff-lcs (>= 1.2.0, < 2.0)
30
30
  rspec-support (~> 3.10.0)
31
- rspec-mocks (3.10.0)
31
+ rspec-mocks (3.10.2)
32
32
  diff-lcs (>= 1.2.0, < 2.0)
33
33
  rspec-support (~> 3.10.0)
34
- rspec-support (3.10.0)
34
+ rspec-support (3.10.2)
35
35
 
36
36
  PLATFORMS
37
37
  ruby
38
38
 
39
39
  DEPENDENCIES
40
- pry (~> 0.13)
41
- rake (~> 12.0)
40
+ pry (~> 0.14)
41
+ rake (~> 13.0)
42
42
  turbo_tests!
43
43
 
44
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
@@ -37,15 +33,7 @@ Finished in 1 minute 35.05 seconds (files took 6.26 seconds to load)
37
33
 
38
34
  ```bash
39
35
  $ bundle exec turbo_tests
40
- ...........................................................................................................................................................................................
41
- ...........................................................................................................................................................................................
42
- ...........................................................................................................................................................................................
43
- .........................................................................................................................................F.................................................
44
- .......................................................................................F...................................................................................................
45
- ...........................................................................................................................................................................................
46
- ...........................................................................................................................................................................................
47
- ...........................................................................................................................................................................................
48
- ...........................................................................................................................................................................................
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
@@ -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
@@ -0,0 +1,15 @@
1
+ RSpec.describe "Fixture of spec file with pending failed examples" do
2
+ it "is implemented but skipped with 'pending'" do
3
+ pending("TODO: skipped with 'pending'")
4
+
5
+ expect(2).to eq(3)
6
+ end
7
+
8
+ it "is implemented but skipped with 'skip'", skip: "TODO: skipped with 'skip'" do
9
+ expect(100).to eq(500)
10
+ end
11
+
12
+ xit "is implemented but skipped with 'xit'" do
13
+ expect(1).to eq(42)
14
+ end
15
+ end
data/lib/turbo_tests.rb CHANGED
@@ -23,28 +23,29 @@ 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
37
37
  end
38
38
 
39
- FakeExecutionResult = Struct.new(:example_skipped?, :pending_message, :status, :pending_fixed?, :exception)
39
+ FakeExecutionResult = Struct.new(:example_skipped?, :pending_message, :status, :pending_fixed?, :exception, :pending_exception)
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
+ FakeException.from_obj(obj[:exception])
48
49
  )
49
50
  end
50
51
  end
@@ -52,24 +53,24 @@ module TurboTests
52
53
  FakeExample = Struct.new(:execution_result, :location, :description, :full_description, :metadata, :location_rerun_argument)
53
54
  class FakeExample
54
55
  def self.from_obj(obj)
55
- metadata = obj["metadata"]
56
+ metadata = obj[:metadata]
56
57
 
57
- metadata["shared_group_inclusion_backtrace"].map! do |frame|
58
+ metadata[:shared_group_inclusion_backtrace].map! do |frame|
58
59
  RSpec::Core::SharedExampleGroupInclusionStackFrame.new(
59
- frame["shared_group_name"],
60
- frame["inclusion_location"]
60
+ frame[:shared_group_name],
61
+ frame[:inclusion_location]
61
62
  )
62
63
  end
63
64
 
64
- metadata[:shared_group_inclusion_backtrace] = metadata.delete("shared_group_inclusion_backtrace")
65
+ metadata[:shared_group_inclusion_backtrace] = metadata.delete(:shared_group_inclusion_backtrace)
65
66
 
66
67
  new(
67
- FakeExecutionResult.from_obj(obj["execution_result"]),
68
- obj["location"],
69
- obj["description"],
70
- obj["full_description"],
68
+ FakeExecutionResult.from_obj(obj[:execution_result]),
69
+ obj[:location],
70
+ obj[:description],
71
+ obj[:full_description],
71
72
  metadata,
72
- obj["location_rerun_argument"]
73
+ obj[:location_rerun_argument]
73
74
  )
74
75
  end
75
76
 
@@ -17,7 +17,7 @@ module TurboTests
17
17
  fail_fast = nil
18
18
 
19
19
  OptionParser.new { |opts|
20
- opts.banner = <<-BANNER.gsub(/^ /, "")
20
+ opts.banner = <<~BANNER
21
21
  Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('1', '2', '3', ...).
22
22
 
23
23
  Uses parallel_tests under the hood, but reports test results incrementally. Based on Discourse and RubyGems work in this area.
@@ -22,10 +22,14 @@ 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
+ :message,
29
33
  :seed
30
34
  )
31
35
 
@@ -35,37 +39,65 @@ module TurboTests
35
39
  @output = output
36
40
  end
37
41
 
42
+ def start(notification)
43
+ output_row(
44
+ type: :load_summary,
45
+ summary: load_summary_to_json(notification)
46
+ )
47
+ end
48
+
49
+ def example_group_started(notification)
50
+ output_row(
51
+ type: :group_started,
52
+ group: group_to_json(notification)
53
+ )
54
+ end
55
+
56
+ def example_group_finished(notification)
57
+ output_row(
58
+ type: :group_finished,
59
+ group: group_to_json(notification)
60
+ )
61
+ end
62
+
38
63
  def example_passed(notification)
39
64
  output_row(
40
- "type" => :example_passed,
41
- "example" => example_to_json(notification.example)
65
+ type: :example_passed,
66
+ example: example_to_json(notification.example)
42
67
  )
43
68
  end
44
69
 
45
70
  def example_pending(notification)
46
71
  output_row(
47
- "type" => :example_pending,
48
- "example" => example_to_json(notification.example)
72
+ type: :example_pending,
73
+ example: example_to_json(notification.example)
49
74
  )
50
75
  end
51
76
 
52
77
  def example_failed(notification)
53
78
  output_row(
54
- "type" => :example_failed,
55
- "example" => example_to_json(notification.example)
79
+ type: :example_failed,
80
+ example: example_to_json(notification.example)
56
81
  )
57
82
  end
58
83
 
59
84
  def seed(notification)
60
85
  output_row(
61
- "type" => :seed,
62
- "seed" => notification.seed
86
+ type: :seed,
87
+ seed: notification.seed
63
88
  )
64
89
  end
65
90
 
66
91
  def close(notification)
67
92
  output_row(
68
- "type" => :close
93
+ type: :close
94
+ )
95
+ end
96
+
97
+ def message(notification)
98
+ output_row(
99
+ type: :message,
100
+ message: notification.message
69
101
  )
70
102
  end
71
103
 
@@ -74,47 +106,65 @@ module TurboTests
74
106
  def exception_to_json(exception)
75
107
  if exception
76
108
  {
77
- "class_name" => exception.class.name.to_s,
78
- "backtrace" => exception.backtrace,
79
- "message" => exception.message,
80
- "cause" => exception_to_json(exception.cause)
109
+ class_name: exception.class.name.to_s,
110
+ backtrace: exception.backtrace,
111
+ message: exception.message,
112
+ cause: exception_to_json(exception.cause)
81
113
  }
82
114
  end
83
115
  end
84
116
 
85
117
  def execution_result_to_json(result)
86
118
  {
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)
119
+ example_skipped?: result.example_skipped?,
120
+ pending_message: result.pending_message,
121
+ status: result.status,
122
+ pending_fixed?: result.pending_fixed?,
123
+ exception: exception_to_json(result.exception || result.pending_exception)
92
124
  }
93
125
  end
94
126
 
95
127
  def stack_frame_to_json(frame)
96
128
  {
97
- "shared_group_name" => frame.shared_group_name,
98
- "inclusion_location" => frame.inclusion_location
129
+ shared_group_name: frame.shared_group_name,
130
+ inclusion_location: frame.inclusion_location
99
131
  }
100
132
  end
101
133
 
102
134
  def example_to_json(example)
103
135
  {
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) }
136
+ execution_result: execution_result_to_json(example.execution_result),
137
+ location: example.location,
138
+ description: example.description,
139
+ full_description: example.full_description,
140
+ metadata: {
141
+ shared_group_inclusion_backtrace:
142
+ example
143
+ .metadata[:shared_group_inclusion_backtrace]
144
+ .map { |frame| stack_frame_to_json(frame) }
111
145
  },
112
- "location_rerun_argument" => example.location_rerun_argument
146
+ location_rerun_argument: example.location_rerun_argument
147
+ }
148
+ end
149
+
150
+ def load_summary_to_json(notification)
151
+ {
152
+ count: notification.count,
153
+ load_time: notification.load_time,
154
+ }
155
+ end
156
+
157
+ def group_to_json(notification)
158
+ {
159
+ group: {
160
+ description: notification.group.description
161
+ }
113
162
  }
114
163
  end
115
164
 
116
165
  def output_row(obj)
117
- output.puts ENV["RSPEC_FORMATTER_OUTPUT_ID"] + obj.to_json
166
+ output.puts(obj.to_json)
167
+ output.flush
118
168
  end
119
169
  end
120
170
  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
 
@@ -9,7 +11,7 @@ module TurboTests
9
11
  name, outputs = config.values_at(:name, :outputs)
10
12
 
11
13
  outputs.map! do |filename|
12
- filename == "-" ? STDOUT : File.open(filename, "w")
14
+ filename == "-" ? $stdout : File.open(filename, "w")
13
15
  end
14
16
 
15
17
  reporter.add(name, outputs)
@@ -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)
@@ -3,17 +3,27 @@
3
3
  require "json"
4
4
  require "parallel_tests/rspec/runner"
5
5
 
6
+ require_relative "../utils/hash_extension"
7
+
6
8
  module TurboTests
7
9
  class Runner
10
+ using CoreExtensions
11
+
8
12
  def self.run(opts = {})
9
13
  files = opts[:files]
10
14
  formatters = opts[:formatters]
11
15
  tags = opts[:tags]
12
- 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) }
13
19
  verbose = opts.fetch(:verbose, false)
14
20
  fail_fast = opts.fetch(:fail_fast, nil)
15
21
  count = opts.fetch(:count, nil)
16
22
 
23
+ if verbose
24
+ STDERR.puts "VERBOSE"
25
+ end
26
+
17
27
  reporter = Reporter.from_config(formatters, start_time)
18
28
 
19
29
  new(
@@ -33,12 +43,13 @@ module TurboTests
33
43
  @verbose = opts[:verbose]
34
44
  @fail_fast = opts[:fail_fast]
35
45
  @count = opts[:count]
36
-
46
+ @load_time = 0
47
+ @load_count = 0
37
48
  @failure_count = 0
38
- @runtime_log = "tmp/parallel_runtime_rspec.log"
39
49
 
40
50
  @messages = Queue.new
41
51
  @threads = []
52
+ @error = false
42
53
  end
43
54
 
44
55
  def run
@@ -47,17 +58,33 @@ module TurboTests
47
58
  ParallelTests::RSpec::Runner.tests_with_size(@files, {}).size
48
59
  ].min
49
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
70
+
50
71
  tests_in_groups =
51
72
  ParallelTests::RSpec::Runner.tests_in_groups(
52
73
  @files,
53
74
  @num_processes,
54
- runtime_log: @runtime_log
75
+ **group_opts
55
76
  )
56
77
 
78
+ setup_tmp_dir
79
+
80
+ subprocess_opts = {
81
+ record_runtime: use_runtime_info
82
+ }
83
+
57
84
  report_number_of_tests(tests_in_groups)
58
85
 
59
- tests_in_groups.each_with_index do |tests, process_id|
60
- start_regular_subprocess(tests, process_id + 1)
86
+ wait_threads = tests_in_groups.map.with_index do |tests, process_id|
87
+ start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
61
88
  end
62
89
 
63
90
  handle_messages
@@ -66,38 +93,64 @@ module TurboTests
66
93
 
67
94
  @threads.each(&:join)
68
95
 
69
- @reporter.failed_examples.empty?
96
+ @reporter.failed_examples.empty? && wait_threads.map(&:value).all?(&:success?)
70
97
  end
71
98
 
72
- protected
99
+ private
100
+
101
+ def setup_tmp_dir
102
+ begin
103
+ FileUtils.rm_r("tmp/test-pipes")
104
+ rescue Errno::ENOENT
105
+ end
73
106
 
74
- def start_regular_subprocess(tests, process_id)
107
+ FileUtils.mkdir_p("tmp/test-pipes/")
108
+ end
109
+
110
+ def start_regular_subprocess(tests, process_id, **opts)
75
111
  start_subprocess(
76
112
  {"TEST_ENV_NUMBER" => process_id.to_s},
77
113
  @tags.map { |tag| "--tag=#{tag}" },
78
114
  tests,
79
- process_id
115
+ process_id,
116
+ **opts
80
117
  )
81
118
  end
82
119
 
83
- def start_subprocess(env, extra_args, tests, process_id)
120
+ def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
84
121
  if tests.empty?
85
122
  @messages << {
86
- "type" => "exit",
87
- "process_id" => process_id
123
+ type: "exit",
124
+ process_id: process_id
88
125
  }
89
126
  else
90
- require "securerandom"
91
- env["RSPEC_FORMATTER_OUTPUT_ID"] = SecureRandom.uuid
92
- 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
93
146
 
94
147
  command = [
95
148
  ENV["BUNDLE_BIN_PATH"], "exec", "rspec",
96
149
  *extra_args,
97
- "--seed", rand(2**16).to_s,
98
- "--format", "ParallelTests::RSpec::RuntimeLogger",
99
- "--out", @runtime_log,
150
+ "--seed", rand(0xFFFF).to_s,
100
151
  "--format", "TurboTests::JsonRowsFormatter",
152
+ "--out", tmp_filename,
153
+ *record_runtime_options,
101
154
  *tests
102
155
  ]
103
156
 
@@ -110,29 +163,33 @@ module TurboTests
110
163
  STDERR.puts "Process #{process_id}: #{command_str}"
111
164
  end
112
165
 
113
- _stdin, stdout, stderr, _wait_thr = Open3.popen3(env, *command)
166
+ stdin, stdout, stderr, wait_thr = Open3.popen3(env, *command)
167
+ stdin.close
114
168
 
115
169
  @threads <<
116
- Thread.new {
117
- require "json"
118
- stdout.each_line do |line|
119
- result = line.split(env["RSPEC_FORMATTER_OUTPUT_ID"])
120
-
121
- output = result.shift
122
- STDOUT.print(output) unless output.empty?
123
-
124
- message = result.shift
125
- next unless message
126
-
127
- message = JSON.parse(message)
128
- message["process_id"] = process_id
129
- @messages << message
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)
174
+
175
+ message[:process_id] = process_id
176
+ @messages << message
177
+ end
130
178
  end
131
179
 
132
- @messages << {"type" => "exit", "process_id" => process_id}
133
- }
180
+ @messages << {type: "exit", process_id: process_id}
181
+ end
134
182
 
183
+ @threads << start_copy_thread(stdout, STDOUT)
135
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
136
193
  end
137
194
  end
138
195
 
@@ -141,6 +198,7 @@ module TurboTests
141
198
  loop do
142
199
  msg = src.readpartial(4096)
143
200
  rescue EOFError
201
+ src.close
144
202
  break
145
203
  else
146
204
  dst.write(msg)
@@ -153,31 +211,47 @@ module TurboTests
153
211
 
154
212
  loop do
155
213
  message = @messages.pop
156
- case message["type"]
214
+ case message[:type]
157
215
  when "example_passed"
158
- example = FakeExample.from_obj(message["example"])
216
+ example = FakeExample.from_obj(message[:example])
159
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
160
222
  when "example_pending"
161
- example = FakeExample.from_obj(message["example"])
223
+ example = FakeExample.from_obj(message[:example])
162
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
163
230
  when "example_failed"
164
- example = FakeExample.from_obj(message["example"])
231
+ example = FakeExample.from_obj(message[:example])
165
232
  @reporter.example_failed(example)
166
233
  @failure_count += 1
167
234
  if fail_fast_met
168
235
  @threads.each(&:kill)
169
236
  break
170
237
  end
238
+ when "message"
239
+ @reporter.message(message[:message])
171
240
  when "seed"
172
241
  when "close"
242
+ when "error"
243
+ @reporter.error_outside_of_examples
244
+ @error = true
173
245
  when "exit"
174
246
  exited += 1
175
247
  if exited == @num_processes
176
248
  break
177
249
  end
178
250
  else
179
- warn("Unhandled message in main process: #{message}")
251
+ STDERR.puts("Unhandled message in main process: #{message}")
180
252
  end
253
+
254
+ STDOUT.flush
181
255
  end
182
256
  rescue Interrupt
183
257
  end
@@ -186,14 +260,12 @@ module TurboTests
186
260
  !@fail_fast.nil? && @fail_fast >= @failure_count
187
261
  end
188
262
 
189
- private
190
-
191
263
  def report_number_of_tests(groups)
192
264
  name = ParallelTests::RSpec::Runner.test_file_name
193
265
 
194
266
  num_processes = groups.size
195
267
  num_tests = groups.map(&:size).sum
196
- tests_per_process = (num_processes == 0 ? 0 : num_tests / num_processes)
268
+ tests_per_process = (num_processes == 0 ? 0 : num_tests.to_f / num_processes).round
197
269
 
198
270
  puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{tests_per_process} #{name}s per process"
199
271
  end
@@ -1,3 +1,3 @@
1
1
  module TurboTests
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.5"
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
@@ -14,12 +14,12 @@ Gem::Specification.new do |spec|
14
14
  spec.metadata["source_code_uri"] = "https://github.com/serpapi/turbo_tests"
15
15
  spec.metadata["changelog_uri"] = "https://github.com/serpapi/turbo_tests/releases"
16
16
 
17
- spec.required_ruby_version = ">= 2.4.0"
17
+ spec.required_ruby_version = ">= 2.4"
18
18
 
19
- spec.add_dependency "rspec", "~> 3.10.0"
19
+ spec.add_dependency "rspec", "~> 3.10"
20
20
  spec.add_dependency "parallel_tests", "~> 3.3"
21
21
 
22
- spec.add_development_dependency "pry", "~> 0.13"
22
+ spec.add_development_dependency "pry", "~> 0.14"
23
23
 
24
24
  spec.add_runtime_dependency "bundler"
25
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Zub
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-23 00:00:00.000000000 Z
11
+ date: 2021-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 3.10.0
19
+ version: '3.10'
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: 3.10.0
26
+ version: '3.10'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: parallel_tests
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.13'
47
+ version: '0.14'
48
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.13'
54
+ version: '0.14'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -74,6 +74,7 @@ executables:
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - ".github/workflows/snyk_ruby-analysis.yml"
77
78
  - ".github/workflows/tests.yml"
78
79
  - ".gitignore"
79
80
  - ".rspec"
@@ -85,12 +86,15 @@ files:
85
86
  - README.md
86
87
  - Rakefile
87
88
  - bin/turbo_tests
89
+ - fixtures/rspec/errors_outside_of_examples_spec.rb
90
+ - fixtures/rspec/pending_exceptions_spec.rb
88
91
  - lib/turbo_tests.rb
89
92
  - lib/turbo_tests/cli.rb
90
93
  - lib/turbo_tests/json_rows_formatter.rb
91
94
  - lib/turbo_tests/reporter.rb
92
95
  - lib/turbo_tests/runner.rb
93
96
  - lib/turbo_tests/version.rb
97
+ - lib/utils/hash_extension.rb
94
98
  - turbo_tests.gemspec
95
99
  homepage: https://github.com/serpapi/turbo_tests
96
100
  licenses:
@@ -107,7 +111,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
111
  requirements:
108
112
  - - ">="
109
113
  - !ruby/object:Gem::Version
110
- version: 2.4.0
114
+ version: '2.4'
111
115
  required_rubygems_version: !ruby/object:Gem::Requirement
112
116
  requirements:
113
117
  - - ">="