rspec-agents 0.1.2 → 0.1.3

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: 499cd9647c7ce1702786bb9d597089a0e9f8d3c56587dd6e9b0e2283ca512dba
4
- data.tar.gz: d5d9774ebd6ebf22bb13faf952081c240740c2b2623f277c04de575788361be7
3
+ metadata.gz: f32aaad8fd16cbd4acd7406452a9b070fbde786ce582fe82964ed846a9547420
4
+ data.tar.gz: 8132da0b079c695d7f763d22bb65527672856dd1b7f4acf1659f5f580afd1769
5
5
  SHA512:
6
- metadata.gz: 21bdfb0dd7a6c3ab6442901dbf81827403f2b480d9bfa099b2474ad7b8f20bc20935ebb98e31c88cebe9be41746d630049070ac02fecc684a1b84d07ae505df1
7
- data.tar.gz: b77860c9f87960fcbbda50188347ef7a05af71b15eb9e59e7e15ea3bb04c14b3fc276115271e96fb883172986d8b53e378a822e08eadfc6d8493b4b4cd6ce804
6
+ metadata.gz: 102605b1124b6b0c0f052465e3347ccd5779e3218c797e909a702c8ff282fca8b015100b8002f830637cfe38c55a9e2defe7447dcff0a3f06601c61d9de56141
7
+ data.tar.gz: 732cc6a14e67137fadfaf66bd960a81443a500dada2f206ab462220d8945543594fe0777fe6eac3c061210ca664e8a399a5bb5df087a5283a7187ce927589d9e
data/bin/rspec-agents CHANGED
@@ -4,8 +4,7 @@
4
4
  # Unified CLI for rspec-agents
5
5
  #
6
6
  # Usage:
7
- # rspec-agents [run] [options] [paths...] # Single-process (default)
8
- # rspec-agents parallel [options] [paths...] # Parallel with workers
7
+ # rspec-agents [options] [paths...] # Run specs (use -w for parallel)
9
8
  # rspec-agents render <json_file> [options] # Render HTML from JSON
10
9
  # rspec-agents worker # Internal: worker mode
11
10
 
@@ -6,23 +6,24 @@ module RSpec
6
6
  module Agents
7
7
  # Unified CLI for rspec-agents
8
8
  #
9
- # Commands:
10
- # run - Single-process execution (default)
11
- # parallel - Parallel execution with worker processes
12
- # worker - Internal: run as a worker subprocess
9
+ # Subcommands:
10
+ # render - Generate HTML report from JSON file
11
+ # worker - Internal: run as a worker subprocess
12
+ #
13
+ # Without a subcommand, runs specs directly. Pass -w/--workers to
14
+ # enable parallel execution.
13
15
  #
14
16
  # @example Single process
15
17
  # CLI.run(["spec/"])
16
- # CLI.run(["run", "spec/"])
17
18
  #
18
19
  # @example Parallel
19
- # CLI.run(["parallel", "-w", "4", "spec/"])
20
+ # CLI.run(["-w", "4", "spec/"])
20
21
  #
21
22
  # @example Worker (internal)
22
23
  # CLI.run(["worker"])
23
24
  #
24
25
  class CLI
25
- COMMANDS = %w[run parallel worker render].freeze
26
+ SUBCOMMANDS = %w[worker render].freeze
26
27
 
27
28
  def self.run(argv)
28
29
  new(argv).run
@@ -33,159 +34,86 @@ module RSpec
33
34
  end
34
35
 
35
36
  def run
36
- command, args = parse_command(@argv)
37
+ command, args = extract_subcommand(@argv)
37
38
 
38
39
  case command
39
- when "run"
40
- run_single(args)
41
- when "parallel"
42
- run_parallel(args)
43
40
  when "worker"
44
41
  run_worker(args)
45
42
  when "render"
46
43
  run_render(args)
47
44
  else
48
- # Default to single-process run
49
- run_single(@argv)
45
+ run_specs(args)
50
46
  end
51
47
  end
52
48
 
53
49
  private
54
50
 
55
- def parse_command(argv)
51
+ def extract_subcommand(argv)
56
52
  return [nil, argv] if argv.empty?
57
53
 
58
54
  first = argv.first
59
-
60
- # Auto-detect parallel mode if -w/--workers flag is present
61
- if has_parallel_flag?(argv)
62
- # If first arg is explicit command, consume it; otherwise keep all args
63
- if COMMANDS.include?(first)
64
- return ["parallel", argv[1..]]
65
- else
66
- return ["parallel", argv]
67
- end
68
- end
69
-
70
- # Original logic for explicit commands or default to single-process
71
- if COMMANDS.include?(first)
55
+ if SUBCOMMANDS.include?(first)
72
56
  [first, argv[1..]]
73
- elsif first.start_with?("-")
74
- # Flag, not a command - default to run
75
- [nil, argv]
76
57
  else
77
- # Path or unknown - default to run
78
58
  [nil, argv]
79
59
  end
80
60
  end
81
61
 
82
- def has_parallel_flag?(argv)
83
- argv.any? { |arg| arg == "-w" || arg.start_with?("--workers") }
84
- end
85
-
86
62
  # =========================================================================
87
- # Single-process mode
63
+ # Spec execution (single-process or parallel via -w)
88
64
  # =========================================================================
89
65
 
90
- def run_single(args)
91
- options = parse_single_options(args)
92
-
93
- runner = Runners::TerminalRunner.new(
94
- output: $stdout,
95
- color: options[:color],
96
- json_path: options[:json_path],
97
- html_path: options[:html_path],
98
- upload_url: options[:upload_url]
99
- )
100
- runner.run(options[:paths])
101
- end
102
-
103
- def parse_single_options(args)
104
- options = { paths: [], color: nil, json_path: nil, html_path: nil, upload_url: nil }
105
-
106
- parser = OptionParser.new do |opts|
107
- opts.banner = "Usage: rspec-agents [run] [options] [paths...]"
108
- opts.separator ""
109
- opts.separator "Run specs in a single process with terminal output."
110
- opts.separator ""
111
- opts.separator "Options:"
112
-
113
- opts.on("--[no-]color", "Force color on/off (default: auto)") do |v|
114
- options[:color] = v
115
- end
116
-
117
- opts.on("--ui MODE", [:interactive, :interleaved, :quiet],
118
- "Output mode (ignored in single-process mode)") do |_mode|
119
- # Accepted for CLI compatibility with parallel mode, but ignored
120
- end
121
-
122
- opts.on("--json PATH", "Save JSON run data to file") do |path|
123
- options[:json_path] = path
124
- end
125
-
126
- opts.on("--html PATH", "Render HTML report to path") do |path|
127
- options[:html_path] = path
128
- end
129
-
130
- opts.on("--upload [URL]", "Upload run data to agents-studio (default: http://localhost:9292)") do |url|
131
- options[:upload_url] = url || "http://localhost:9292"
132
- end
133
-
134
- opts.on("-h", "--help", "Show this help") do
135
- puts opts
136
- exit 0
137
- end
66
+ def run_specs(args)
67
+ options = parse_run_options(args)
68
+
69
+ if options[:workers]
70
+ runner = Runners::ParallelTerminalRunner.new(
71
+ worker_count: options[:workers],
72
+ fail_fast: options[:fail_fast],
73
+ output: $stdout,
74
+ color: options[:color],
75
+ json_path: options[:json_path],
76
+ html_path: options[:html_path],
77
+ ui_mode: options[:ui_mode],
78
+ upload_url: options[:upload_url]
79
+ )
80
+ else
81
+ runner = Runners::TerminalRunner.new(
82
+ output: $stdout,
83
+ color: options[:color],
84
+ json_path: options[:json_path],
85
+ html_path: options[:html_path],
86
+ upload_url: options[:upload_url]
87
+ )
138
88
  end
139
89
 
140
- remaining = parser.parse(args)
141
- options[:paths] = remaining.empty? ? ["spec"] : remaining
142
- options
143
- end
144
-
145
- # =========================================================================
146
- # Parallel mode
147
- # =========================================================================
148
-
149
- def run_parallel(args)
150
- options = parse_parallel_options(args)
151
-
152
- runner = Runners::ParallelTerminalRunner.new(
153
- worker_count: options[:workers],
154
- fail_fast: options[:fail_fast],
155
- output: $stdout,
156
- color: options[:color],
157
- json_path: options[:json_path],
158
- html_path: options[:html_path],
159
- ui_mode: options[:ui_mode],
160
- upload_url: options[:upload_url]
161
- )
162
90
  runner.run(options[:paths])
163
91
  end
164
92
 
165
- def parse_parallel_options(args)
93
+ def parse_run_options(args)
166
94
  options = {
167
- workers: 4,
168
- fail_fast: false,
169
- paths: [],
170
- color: nil,
171
- json_path: nil,
172
- html_path: nil,
173
- ui_mode: nil,
95
+ paths: [],
96
+ workers: nil,
97
+ fail_fast: false,
98
+ color: nil,
99
+ json_path: nil,
100
+ html_path: nil,
101
+ ui_mode: nil,
174
102
  upload_url: nil
175
103
  }
176
104
 
177
105
  parser = OptionParser.new do |opts|
178
- opts.banner = "Usage: rspec-agents parallel [options] [paths...]"
106
+ opts.banner = "Usage: rspec-agents [options] [paths...]"
179
107
  opts.separator ""
180
- opts.separator "Run specs in parallel across multiple worker processes."
108
+ opts.separator "Run specs with terminal output. Pass -w to run in parallel."
181
109
  opts.separator ""
182
110
  opts.separator "Options:"
183
111
 
184
- opts.on("-w", "--workers COUNT", Integer, "Number of workers (default: 4)") do |w|
112
+ opts.on("-w", "--workers COUNT", Integer, "Run in parallel with COUNT workers") do |w|
185
113
  options[:workers] = w
186
114
  end
187
115
 
188
- opts.on("--fail-fast", "Stop on first failure") do
116
+ opts.on("--fail-fast", "Stop on first failure (parallel mode)") do
189
117
  options[:fail_fast] = true
190
118
  end
191
119
 
@@ -194,7 +122,7 @@ module RSpec
194
122
  end
195
123
 
196
124
  opts.on("--ui MODE", [:interactive, :interleaved, :quiet],
197
- "Output mode: interactive, interleaved, quiet (default: auto)") do |mode|
125
+ "Output mode: interactive, interleaved, quiet (parallel mode, default: auto)") do |mode|
198
126
  options[:ui_mode] = mode
199
127
  end
200
128
 
@@ -3,7 +3,7 @@
3
3
  require "async"
4
4
  require "fileutils"
5
5
  require_relative "../parallel/ui/ui_factory"
6
- require_relative "run_data_uploader"
6
+ require_relative "streaming_run_data_uploader"
7
7
 
8
8
  module RSpec
9
9
  module Agents
@@ -94,6 +94,12 @@ module RSpec
94
94
  executor.on_event { |type, event| route_event_to_ui(type, event) }
95
95
  executor.on_progress { |c, t, f| @ui.on_progress(completed: c, total: t, failures: f) }
96
96
 
97
+ # Wire up streaming upload if --upload specified
98
+ if @upload_url
99
+ streaming_uploader = StreamingRunDataUploader.new(url: @upload_url, output: @output)
100
+ attach_streaming_upload(streaming_uploader, executor)
101
+ end
102
+
97
103
  # Start UI
98
104
  @ui.on_run_started(worker_count: @worker_count, example_count: examples.size)
99
105
  @ui.start_input_handling
@@ -114,8 +120,11 @@ module RSpec
114
120
  print_failed_examples_filter
115
121
  end
116
122
 
117
- # Save outputs
118
- save_outputs(executor.run_data) if @json_path || @html_path || @upload_url
123
+ # Finish streaming upload (blocks until queue drained)
124
+ streaming_uploader&.finish
125
+
126
+ # Save file outputs
127
+ save_outputs(executor.run_data) if @json_path || @html_path
119
128
 
120
129
  result&.success? ? 0 : 1
121
130
  ensure
@@ -204,6 +213,15 @@ module RSpec
204
213
  "#{COLORS[color]}#{text}#{COLORS[:reset]}"
205
214
  end
206
215
 
216
+ def attach_streaming_upload(uploader, executor)
217
+ executor.on_event do |type, _event|
218
+ uploader.start(executor.run_data) if type == "SuiteStarted"
219
+ end
220
+ executor.on_example_completed do |event, run_data|
221
+ uploader.upload_example(event, run_data)
222
+ end
223
+ end
224
+
207
225
  def save_outputs(run_data)
208
226
  return unless run_data
209
227
 
@@ -216,10 +234,6 @@ module RSpec
216
234
  if @html_path
217
235
  Serialization::TestSuiteRenderer.render(run_data, output_path: @html_path)
218
236
  end
219
-
220
- if @upload_url
221
- RunDataUploader.new(url: @upload_url, output: @output).upload(run_data)
222
- end
223
237
  end
224
238
 
225
239
  def populate_rendered_extensions(run_data)
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+
7
+ module RSpec
8
+ module Agents
9
+ module Runners
10
+ # Uploads run data to an agents-studio webapp incrementally, one example at a time.
11
+ # Each upload sends a minimal RunData containing run metadata + just that example.
12
+ # The receiving /api/import endpoint is idempotent with respect to run_id, merging
13
+ # examples into the same run record.
14
+ #
15
+ # Uses a background thread with a Queue so uploads never block spec execution.
16
+ #
17
+ # @example
18
+ # uploader = StreamingRunDataUploader.new(url: "http://localhost:9292", output: $stdout)
19
+ # uploader.start(run_data)
20
+ # executor.on_example_completed { |event, rd| uploader.upload_example(event, rd) }
21
+ # # ... after suite finishes ...
22
+ # uploader.finish
23
+ #
24
+ class StreamingRunDataUploader
25
+ TIMEOUT = 10 # seconds per request
26
+
27
+ # @param url [String] Base URL of the agents-studio webapp
28
+ # @param output [IO] Output stream for status messages
29
+ def initialize(url:, output: $stdout)
30
+ @url = url.chomp("/")
31
+ @output = output
32
+ @queue = Queue.new
33
+ @failed_examples = {}
34
+ @mutex = Mutex.new
35
+ @worker_thread = nil
36
+ @http = nil
37
+ @uploaded_count = 0
38
+ @run_id = nil
39
+ end
40
+
41
+ # Whether the uploader has been started.
42
+ def started?
43
+ !!@run_id
44
+ end
45
+
46
+ # Start the background upload worker.
47
+ # Idempotent — safe to call multiple times; only the first call takes effect.
48
+ # @param run_data [Serialization::RunData] The run data (used for metadata)
49
+ def start(run_data)
50
+ return if started?
51
+
52
+ @run_id = run_data.run_id
53
+ @run_metadata = {
54
+ run_id: run_data.run_id,
55
+ started_at: run_data.started_at,
56
+ seed: run_data.seed,
57
+ git_commit: run_data.git_commit
58
+ }
59
+ @output.puts "Streaming run data to #{@url}..."
60
+ @worker_thread = Thread.new { process_queue }
61
+ end
62
+
63
+ # Enqueue a single example for upload.
64
+ # Meant to be called from an on_example_completed callback.
65
+ # @param event [Events::ExamplePassed, Events::ExampleFailed, Events::ExamplePending]
66
+ # @param run_data [Serialization::RunData] Current run data
67
+ def upload_example(event, run_data)
68
+ return unless @run_id
69
+
70
+ example_data = run_data&.example(event.example_id)
71
+ return unless example_data
72
+
73
+ @queue.push([:upload, { example_id: event.example_id, example_data: example_data }])
74
+ end
75
+
76
+ # Signal the worker to finish: retry failed examples, then drain and stop.
77
+ # Blocks until the worker thread completes (up to 60s).
78
+ def finish
79
+ return unless @worker_thread
80
+
81
+ @queue.push([:finish, nil])
82
+ @worker_thread.join(60)
83
+ close_http
84
+
85
+ if @uploaded_count > 0
86
+ @output.puts "Upload complete: #{@uploaded_count} example#{"s" unless @uploaded_count == 1} streamed"
87
+ end
88
+
89
+ if @failed_examples.any?
90
+ @output.puts "Warning: #{@failed_examples.size} example(s) failed to upload"
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def process_queue
97
+ loop do
98
+ action, payload = @queue.pop
99
+
100
+ case action
101
+ when :upload
102
+ do_upload_example(payload)
103
+ when :finish
104
+ retry_failed_examples
105
+ break
106
+ end
107
+ end
108
+ rescue => e
109
+ @output.puts "Upload worker error: #{e.message}"
110
+ end
111
+
112
+ def do_upload_example(payload)
113
+ example_data = payload[:example_data]
114
+ stable_id = example_data.stable_id || payload[:example_id]
115
+
116
+ # Build a minimal RunData with just this one example
117
+ body = build_single_example_payload(example_data)
118
+ post("/api/import", body)
119
+
120
+ @mutex.synchronize do
121
+ @uploaded_count += 1
122
+ @failed_examples.delete(stable_id)
123
+ end
124
+ rescue => e
125
+ @mutex.synchronize do
126
+ @failed_examples[stable_id] = payload
127
+ end
128
+ end
129
+
130
+ def retry_failed_examples
131
+ failed = @mutex.synchronize { @failed_examples.dup }
132
+ failed.each do |stable_id, payload|
133
+ do_upload_example(payload)
134
+ rescue
135
+ # Leave in @failed_examples for final warning
136
+ end
137
+ end
138
+
139
+ def build_single_example_payload(example_data)
140
+ {
141
+ run_id: @run_metadata[:run_id],
142
+ started_at: serialize_time(@run_metadata[:started_at]),
143
+ seed: @run_metadata[:seed],
144
+ git_commit: @run_metadata[:git_commit],
145
+ scenarios: {},
146
+ examples: {
147
+ example_data.id => example_data.to_h
148
+ }
149
+ }
150
+ end
151
+
152
+ def serialize_time(time)
153
+ return nil unless time
154
+ time.is_a?(Time) ? time.iso8601(3) : time
155
+ end
156
+
157
+ def http_connection
158
+ @http ||= begin
159
+ uri = URI.parse(@url)
160
+ http = Net::HTTP.new(uri.host, uri.port)
161
+ http.use_ssl = (uri.scheme == "https")
162
+ http.open_timeout = TIMEOUT
163
+ http.read_timeout = TIMEOUT
164
+ http.start
165
+ http
166
+ end
167
+ end
168
+
169
+ def close_http
170
+ @http&.finish
171
+ rescue
172
+ # Ignore close errors
173
+ ensure
174
+ @http = nil
175
+ end
176
+
177
+ def post(path, body)
178
+ uri = URI.parse("#{@url}#{path}")
179
+ request = Net::HTTP::Post.new(uri.path)
180
+ request["Content-Type"] = "application/json"
181
+ request.body = JSON.generate(body)
182
+
183
+ response = http_connection.request(request)
184
+ unless response.is_a?(Net::HTTPSuccess)
185
+ raise "HTTP #{response.code}: #{response.body}"
186
+ end
187
+ response
188
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, IOError, Net::OpenTimeout, Net::ReadTimeout => e
189
+ close_http # Reset connection on transport errors
190
+ raise
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
- require_relative "run_data_uploader"
4
+ require_relative "streaming_run_data_uploader"
5
5
 
6
6
  module RSpec
7
7
  module Agents
@@ -50,10 +50,19 @@ module RSpec
50
50
  # Wire up event handling for terminal display
51
51
  executor.on_event { |type, event| handle_event(type, event) }
52
52
 
53
+ # Wire up streaming upload if --upload specified
54
+ if @upload_url
55
+ streaming_uploader = StreamingRunDataUploader.new(url: @upload_url, output: @output)
56
+ attach_streaming_upload(streaming_uploader, executor)
57
+ end
58
+
53
59
  result = executor.execute(Array(files_or_args))
54
60
 
55
- # Save outputs after run completes
56
- save_outputs(executor.run_data) if @json_path || @html_path || @upload_url
61
+ # Finish streaming upload (blocks until queue drained)
62
+ streaming_uploader&.finish
63
+
64
+ # Save file outputs after run completes
65
+ save_outputs(executor.run_data) if @json_path || @html_path
57
66
 
58
67
  result.success? ? 0 : 1
59
68
  end
@@ -166,6 +175,15 @@ module RSpec
166
175
  "#{COLORS[color]}#{text}#{COLORS[:reset]}"
167
176
  end
168
177
 
178
+ def attach_streaming_upload(uploader, executor)
179
+ executor.on_event do |type, _event|
180
+ uploader.start(executor.run_data) if type == "SuiteStarted"
181
+ end
182
+ executor.on_example_completed do |event, run_data|
183
+ uploader.upload_example(event, run_data)
184
+ end
185
+ end
186
+
169
187
  def save_outputs(run_data)
170
188
  return unless run_data
171
189
 
@@ -178,10 +196,6 @@ module RSpec
178
196
  if @html_path
179
197
  Serialization::TestSuiteRenderer.render(run_data, output_path: @html_path)
180
198
  end
181
-
182
- if @upload_url
183
- RunDataUploader.new(url: @upload_url, output: @output).upload(run_data)
184
- end
185
199
  end
186
200
 
187
201
  def populate_rendered_extensions(run_data)
@@ -55,6 +55,14 @@ module RSpec
55
55
  @conversation.current_topic
56
56
  end
57
57
 
58
+ # Get tool calls from the current response, optionally filtered by name
59
+ # @param name [Symbol, String, nil] Optional tool name to filter by
60
+ # @return [Array<ToolCall>]
61
+ def tool_calls(name = nil)
62
+ calls = @current_response&.tool_calls || []
63
+ name ? calls.select { |tc| tc.name == name.to_sym } : calls
64
+ end
65
+
58
66
  # Check if current turn is in expected topic
59
67
  #
60
68
  # @param expected_topic [Symbol] Expected topic name
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module Agents
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frederik Fix
@@ -186,7 +186,7 @@ files:
186
186
  - lib/rspec/agents/prompt_builders/user_simulation.rb
187
187
  - lib/rspec/agents/runners/headless_runner.rb
188
188
  - lib/rspec/agents/runners/parallel_terminal_runner.rb
189
- - lib/rspec/agents/runners/run_data_uploader.rb
189
+ - lib/rspec/agents/runners/streaming_run_data_uploader.rb
190
190
  - lib/rspec/agents/runners/terminal_runner.rb
191
191
  - lib/rspec/agents/runners/user_simulator.rb
192
192
  - lib/rspec/agents/scenario.rb
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "uri"
5
- require "json"
6
-
7
- module RSpec
8
- module Agents
9
- module Runners
10
- # Uploads run data to an agents-studio webapp via HTTP POST.
11
- # Used by TerminalRunner and ParallelTerminalRunner when --upload is specified.
12
- #
13
- # @example
14
- # uploader = RunDataUploader.new(url: "http://localhost:9292")
15
- # uploader.upload(run_data) # => true/false
16
- #
17
- class RunDataUploader
18
- TIMEOUT = 30 # seconds
19
-
20
- # @param url [String] Base URL of the agents-studio webapp
21
- # @param output [IO] Output stream for status messages
22
- def initialize(url:, output: $stdout)
23
- @url = url.chomp("/")
24
- @output = output
25
- end
26
-
27
- # Upload run data to the webapp.
28
- # @param run_data [Serialization::RunData] The run data to upload
29
- # @return [Boolean] true if upload succeeded
30
- def upload(run_data)
31
- return false unless run_data
32
-
33
- uri = URI.parse("#{@url}/api/import")
34
- json_body = JSON.generate(run_data.to_h)
35
-
36
- @output.puts "Uploading run data to #{@url}..."
37
-
38
- http = Net::HTTP.new(uri.host, uri.port)
39
- http.use_ssl = (uri.scheme == "https")
40
- http.open_timeout = TIMEOUT
41
- http.read_timeout = TIMEOUT
42
-
43
- request = Net::HTTP::Post.new(uri.path)
44
- request["Content-Type"] = "application/json"
45
- request.body = json_body
46
-
47
- response = http.request(request)
48
-
49
- if response.is_a?(Net::HTTPSuccess)
50
- result = JSON.parse(response.body)
51
- @output.puts "Upload complete: #{result["example_count"]} examples " \
52
- "(#{result["passed_count"]} passed, #{result["failed_count"]} failed)"
53
- true
54
- else
55
- @output.puts "Upload failed (HTTP #{response.code}): #{response.body}"
56
- false
57
- end
58
- rescue Errno::ECONNREFUSED
59
- @output.puts "Upload failed: could not connect to #{@url} (is agents-studio running?)"
60
- false
61
- rescue Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout
62
- @output.puts "Upload failed: connection to #{@url} timed out"
63
- false
64
- rescue StandardError => e
65
- @output.puts "Upload failed: #{e.message}"
66
- false
67
- end
68
- end
69
- end
70
- end
71
- end