fast_ci 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,287 @@
1
+ module Minitest
2
+ module Reporters
3
+ class Suite
4
+ attr_reader :name
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def ==(other)
10
+ name == other.name
11
+ end
12
+
13
+ def eql?(other)
14
+ self == other
15
+ end
16
+
17
+ def hash
18
+ name.hash
19
+ end
20
+
21
+ def to_s
22
+ name.to_s
23
+ end
24
+ end
25
+
26
+ class FastCIReporter
27
+ attr_accessor :tests, :test_results, :ids
28
+
29
+ def initialize
30
+ @tests = {}
31
+ @test_results = {}
32
+ @ids = {}
33
+ @events = []
34
+
35
+ $stdout = StringIO.new()
36
+
37
+ if ENV['RBCI_REMOTE_TESTS'] != 'true'
38
+ FastCI.minitest_ws.on(:enq_request) do
39
+ tests
40
+ end
41
+
42
+ FastCI.minitest_ws.on(:deq) do |api_tests|
43
+ test_results
44
+ end
45
+ end
46
+ end
47
+
48
+ def start
49
+ test_count = Runnable.runnables.sum { |s| s.runnable_methods.count }
50
+ msg('start', { test_count: test_count })
51
+ @events << ['run_minitest'.upcase, { started_at: Time.current }]
52
+ send_events if ENV['RBCI_REMOTE_TESTS'] == 'true'
53
+ end
54
+
55
+ def get_output
56
+ return if $stdout.pos == 0
57
+ $stdout.rewind
58
+ res = $stdout.read
59
+ $stdout.flush
60
+ $stdout.rewind
61
+ return unless res
62
+ res.strip.chomp if res.strip.chomp != ""
63
+ end
64
+
65
+ def before_test(test)
66
+ $stdout = StringIO.new()
67
+ end
68
+
69
+ def prerecord(klass, name)
70
+ description = test_description(name)
71
+ path = test_path(klass.name)
72
+
73
+ test_results[path] ||= { run_time: 0.0, file_status: 'pending', test_count: 0, test_counters: { failed: 0, passed: 0, pending: 0 }, '1' => { description: klass.name } }
74
+ test_results[path][:test_count] += 1
75
+
76
+ id = (test_results[path]['1'].keys.size + 1).to_s
77
+ ids[description] = id
78
+
79
+ test_results[path]['1'][id] ||= { status: 'pending', description: description }
80
+ test_results[path]['1'][id][:start] = Minitest.clock_time
81
+
82
+ tests[path] ||= { run_time: 0.0, file_status: 'pending', test_count: 0, test_counters: { failed: 0, passed: 0, pending: 0 }, '1' => {} }
83
+ tests[path][:test_count] += 1
84
+ tests[path]['1'][id] ||= { status: 'pending' }
85
+ end
86
+
87
+ def record(result)
88
+ test_finished(result)
89
+ description = test_description(result.name)
90
+ id = ids[description]
91
+ path = test_path(result.klass)
92
+
93
+ test_results[path]['1'][id][:end] = Minitest.clock_time
94
+ test_results[path]['1'][id][:run_time] = test_results[path]['1'][id][:end] - test_results[path]['1'][id][:start]
95
+ test_results[path]['1'][id][:status] = result_status(result).to_s
96
+ test_results[path][:test_counters][result_status(result)] += 1
97
+ test_results[path][:run_time] += test_results[path]['1'][id][:run_time]
98
+ end
99
+
100
+ def report
101
+ test_results.each do |path, file_results|
102
+ file_status = 'pending'
103
+ file_results['1'].each do |id, test_result|
104
+ next if id == :description
105
+ if (test_result[:status] == 'passed') && (file_status != 'failed')
106
+ file_status = 'passed'
107
+ elsif file_status == 'failed'
108
+ file_status = 'failed'
109
+ end
110
+ end
111
+ test_results[path][:file_status] = file_status
112
+ end
113
+
114
+ if ENV['RBCI_REMOTE_TESTS'] == 'true'
115
+ send_events
116
+ else
117
+ FastCI.minitest_await
118
+ end
119
+ end
120
+
121
+ def passed?
122
+ results = []
123
+ test_results.map do |path, file_results|
124
+ file_results['1'].each do |id, test_result|
125
+ next if id == :description
126
+ if test_result[:status] == 'failed'
127
+ results << false
128
+ else
129
+ results << true
130
+ end
131
+ end
132
+ end
133
+
134
+ pass = results.any? {|reult| !result }
135
+
136
+ if pass
137
+ @events << ['run_minitest'.upcase, { succeed_after: 1 }]
138
+ else
139
+ @events << ['run_minitest'.upcase, { failed_after: 1 }]
140
+ end
141
+ send_events if ENV['RBCI_REMOTE_TESTS'] == 'true'
142
+
143
+ return pass
144
+ end
145
+
146
+ def method_missing(method, *args)
147
+ return
148
+ end
149
+
150
+ protected
151
+
152
+ def before_suite(suite)
153
+ end
154
+
155
+ def after_suite(_suite)
156
+ end
157
+
158
+ def record_print_status(test)
159
+ test_name = test.name.gsub(/^test_: /, "test:")
160
+ print pad_test(test_name)
161
+ print_colored_status(test)
162
+ print(" (%.2fs)" % test.time) unless test.time.nil?
163
+ puts
164
+ end
165
+
166
+ def record_print_failures_if_any(test)
167
+ if !test.skipped? && test.failure
168
+ print_info(test.failure, test.error?)
169
+ puts
170
+ end
171
+ end
172
+
173
+ def screenshots_base64(output)
174
+ return unless output
175
+ img_path = output&.scan(/\\[Screenshot Image\\]: (.*)$/)&.flatten&.first&.strip&.chomp ||
176
+ output&.scan(/\\[Screenshot\\]: (.*)$/)&.flatten&.first&.strip&.chomp
177
+
178
+ if img_path && File.exist?(img_path)
179
+ STDOUT.puts "SCREENSHOT!"
180
+ Base64.strict_encode64(File.read(img_path))
181
+ end
182
+ end
183
+
184
+ def test_finished(test)
185
+ output = get_output
186
+
187
+ location = if !test.source_location.join(":").start_with?(::Rails.root.join('vendor').to_s)
188
+ test.source_location.join(":")
189
+ else
190
+ if (file = `cat #{::Rails.root.join('vendor', 'bundle', 'minitest_cache_file').to_s} | grep "#{test.klass} => "`.split(" => ").last&.chomp)
191
+ file + ":"
192
+ else
193
+ file = `grep -rw "#{::Rails.root.to_s}" -e "#{test.klass} "`.split(":").first
194
+ `echo "#{test.klass} => #{file}" >> #{::Rails.root.join('vendor', 'bundle', 'minitest_cache_file').to_s}`
195
+ file + ":"
196
+ end
197
+ end
198
+
199
+ fully_formatted = if test.failure
200
+ fully_formatted = "\n" + test.failure.message.split("\n").first
201
+
202
+ test.failure.backtrace.each do |l|
203
+ if !l["/cache/"]
204
+ fully_formatted << "\n " + cyan + l + "\033[0m"
205
+ end
206
+ end
207
+
208
+ fully_formatted
209
+ end
210
+
211
+ output_inside = output&.split("\n")&.select do |line|
212
+ !line["Screenshot"]
213
+ end&.join("\n")
214
+ event_data = {
215
+ test_class: Suite.new(test.klass),
216
+ test_name: test.name.gsub(/^test_\\d*/, "").gsub(/^test_: /, "test:").gsub(/^_/, "").strip,
217
+ assertions_count: test.assertions,
218
+ location: location,
219
+ status: status(test),
220
+ run_time: test.time,
221
+ fully_formatted: fully_formatted,
222
+ output_inside: output_inside,
223
+ screenshots_base64: [screenshots_base64(output)]
224
+ }
225
+
226
+ msg('test_finished', event_data)
227
+ if ENV['RBCI_REMOTE_TESTS'] == 'true'
228
+ send_events if @events.length >= 10
229
+ end
230
+ end
231
+
232
+ def status(test)
233
+ if test.passed?
234
+ "passed"
235
+ elsif test.error?
236
+ "error"
237
+ elsif test.skipped?
238
+ "skipped"
239
+ elsif test.failure
240
+ "failed"
241
+ else
242
+ raise("Status not found")
243
+ end
244
+ end
245
+
246
+ private
247
+
248
+ def msg(event, data)
249
+ @events << ["minitest_#{event}".upcase, data]
250
+ end
251
+
252
+ def send_events
253
+ return unless @events.length > 0
254
+
255
+ json_events = {
256
+ build_id: FastCI.configuration.orig_build_id,
257
+ compressed_data: Base64.strict_encode64(Zlib::Deflate.deflate(JSON.fast_generate(@events), 9)),
258
+ }
259
+
260
+ FastCI.send_events(json_events)
261
+
262
+ @events = []
263
+ end
264
+
265
+ def test_description(name)
266
+ test_name = name.split('test_').last
267
+ test_name = test_name[2..-1] if test_name.starts_with?(': ')
268
+
269
+ return test_name.strip
270
+ end
271
+
272
+ def test_path(klass)
273
+ return "./#{Object.const_source_location(klass)[0].gsub(Regexp.new("^#{::Rails.root}/"), '')}"
274
+ end
275
+
276
+ def result_status(result)
277
+ if result.passed?
278
+ :passed
279
+ elsif result.skipped?
280
+ :skipped
281
+ else
282
+ :failed
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "reporters/FastCI_reporter"
2
+
3
+ module Minitest
4
+ def self.plugin_fastci_init(options)
5
+ if ENV['FAST_CI_SECRET_KEY'].present?
6
+ Minitest.reporter << Minitest::Reporters::FastCIReporter.new
7
+ end
8
+ end
9
+ end
metadata CHANGED
@@ -1,29 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Ale
7
+ - Nesha Zoric
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-12 00:00:00.000000000 Z
11
+ date: 2024-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: async-websocket
14
+ name: console
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.19'
19
+ version: 1.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.19'
26
+ version: 1.10.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-websocket
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "<="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.20.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "<="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.20.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubycritic
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 4.1.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 4.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: brakeman
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 5.4.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 5.4.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '5.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '5.1'
27
83
  - !ruby/object:Gem::Dependency
28
84
  name: pry
29
85
  requirement: !ruby/object:Gem::Requirement
@@ -40,26 +96,47 @@ dependencies:
40
96
  version: '0'
41
97
  description: Ruby wrapper for creating FastCI integrations
42
98
  email:
43
- - ale@alexvko.com
44
- executables: []
99
+ - no-reply@ruby.ci
100
+ executables:
101
+ - fastci_rubycritic
102
+ - fastci_brakeman
103
+ - fastci_bundle_audit
45
104
  extensions: []
46
105
  extra_rdoc_files: []
47
106
  files:
107
+ - ".github/workflows/main.yml"
48
108
  - ".gitignore"
49
109
  - ".rspec"
50
110
  - ".rubocop.yml"
111
+ - CODE_OF_CONDUCT.md
51
112
  - Gemfile
52
113
  - Gemfile.lock
53
114
  - LICENSE.txt
54
115
  - README.md
55
116
  - Rakefile
56
117
  - bin/console
118
+ - bin/fastci_brakeman
119
+ - bin/fastci_bundle_audit
120
+ - bin/fastci_rubycritic
57
121
  - bin/setup
58
122
  - fast_ci.gemspec
59
123
  - lib/fast_ci.rb
124
+ - lib/fast_ci/brakeman.rb
125
+ - lib/fast_ci/brakeman/commandline.rb
60
126
  - lib/fast_ci/configuration.rb
127
+ - lib/fast_ci/dryrun_runner_prepend.rb
61
128
  - lib/fast_ci/exceptions.rb
129
+ - lib/fast_ci/extract_definitions.rb
130
+ - lib/fast_ci/rspec_dryrun_formatter.rb
131
+ - lib/fast_ci/rspec_formatter.rb
132
+ - lib/fast_ci/rspec_run_formatter.rb
133
+ - lib/fast_ci/ruby_critic/cli/application.rb
134
+ - lib/fast_ci/runner_prepend.rb
135
+ - lib/fast_ci/simple_cov.rb
136
+ - lib/fast_ci/simple_cov/reporting.rb
62
137
  - lib/fast_ci/version.rb
138
+ - lib/minitest/reporters/fastci_reporter.rb
139
+ - lib/minitest/rubyci_plugin.rb
63
140
  homepage: https://github.com/RubyCI/fast_ci_gem
64
141
  licenses:
65
142
  - MIT