Class: Rubino::Tools::TestTool
- Defined in:
- lib/rubino/tools/test_tool.rb
Overview
Runs the workspace project’s test suite and returns a STRUCTURED result instead of the raw toolchain firehose the ‘shell` tool emits.
Why this exists (issue #101): to run tests the model used to drive ‘shell` and reason its way through the whole Ruby toolchain — bundler version mismatches, missing gems, which command to use. On real tasks that burned several tool calls and twice sent the agent chasing toolchain errors (bundler `GemNotFound`, an `undefined method ’untaint’‘ crash from an old pinned bundler) instead of the user’s actual request; one earlier run even drifted toward ‘gem uninstall bundler` / `rm -rf …`. This tool:
- auto-detects the framework (rspec / minitest / rake) and the right
invocation, preferring `bundle exec` when a Gemfile is present and the
bundle is usable, falling back to the bare runner when it is not (so a
stale lockfile degrades gracefully rather than making the model fight
bundler),
- returns pass/fail counts, the failing examples (name + file:line +
short message) parsed from the runner output, and a short raw tail —
not the full backtrace,
- distinguishes "the suite could not even start" (toolchain error) from
"the suite ran and N failed", via the structured `error_code`.
Execution mirrors ShellTool’s foreground path: own process group, SIGTERM on timeout/cancel, cwd = workspace root (same resolution as ruby/shell).
Constant Summary collapse
- DEFAULT_TIMEOUT =
300- MAX_TIMEOUT =
600- TICK =
0.05- RAW_TAIL_LINES =
Lines of raw runner output to keep for context. Enough to show the tail of a failure dump without dragging the full backtrace into context.
40
Instance Attribute Summary
Attributes inherited from Base
#cancel_token, #read_tracker, #stream_chunk
Instance Method Summary collapse
- #call(arguments) ⇒ Object
- #description ⇒ Object
- #input_schema ⇒ Object
- #name ⇒ Object
-
#risk_level ⇒ Object
Runs project code (the test suite), so gated like ‘ruby`: not destructive, but it does execute arbitrary code.
Methods inherited from Base
#cancellation_requested?, #config_key, #emit_chunk, #risky?, #to_tool_definition, workspace_root, workspace_roots
Instance Method Details
#call(arguments) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/rubino/tools/test_tool.rb', line 83 def call(arguments) args = arguments.is_a?(Hash) ? arguments : {} path = args["path"] || args[:path] override = args["framework"] || args[:framework] timeout = (args["timeout"] || args[:timeout] || DEFAULT_TIMEOUT).to_i timeout = [[timeout, 1].max, MAX_TIMEOUT].min root = resolve_workspace return { output: "Error: cannot access workspace directory", error_code: :workspace_error } unless root framework = (override && !override.to_s.empty? ? override.to_s : detect_framework(root)) unless framework return { output: "Error: no test setup detected in #{root} — looked for " \ "spec/ (.rspec), test/, and a Rakefile. Pass `framework` " \ "to override, or use the shell tool for a custom command.", error_code: :no_test_setup } end command = build_command(root, framework, path) run = execute(command, root, timeout) build_result(framework, command, run) end |
#description ⇒ Object
41 42 43 44 45 46 47 48 49 50 |
# File 'lib/rubino/tools/test_tool.rb', line 41 def description "Run the workspace project's test suite and return a structured result " \ "(framework, command, exit status, example/failure counts, and the " \ "failing examples with file:line and message). Auto-detects RSpec, " \ "Minitest, or a Rakefile default task; prefers `bundle exec` when a " \ "Gemfile is present and falls back to the bare runner if the bundle is " \ "broken. Optional `path` runs a single file or pattern; optional " \ "`framework` (rspec/minitest/rake) overrides detection. Use this " \ "instead of driving `shell` by hand to run tests." end |
#input_schema ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/rubino/tools/test_tool.rb', line 52 def input_schema { type: "object", properties: { path: { type: "string", description: "Optional file or pattern to run a subset (e.g. " \ "'spec/models/user_spec.rb' or 'spec/models/'). " \ "Runs the whole suite when omitted." }, framework: { type: "string", enum: %w[rspec minitest rake], description: "Override framework detection. Omit to auto-detect." }, timeout: { type: "integer", description: "Timeout in seconds (default #{DEFAULT_TIMEOUT}, max #{MAX_TIMEOUT})." } }, required: [] } end |
#name ⇒ Object
37 38 39 |
# File 'lib/rubino/tools/test_tool.rb', line 37 def name "run_tests" end |
#risk_level ⇒ Object
Runs project code (the test suite), so gated like ‘ruby`: not destructive, but it does execute arbitrary code. :medium → asks in manual mode, auto-allowed in auto mode.
79 80 81 |
# File 'lib/rubino/tools/test_tool.rb', line 79 def risk_level :medium end |