spec_scout 0.1.0
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 +7 -0
- data/.idea/.gitignore +10 -0
- data/.idea/Projects.iml +41 -0
- data/.idea/copilot.data.migration.ask2agent.xml +6 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec_status +236 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +72 -0
- data/LICENSE +21 -0
- data/README.md +433 -0
- data/Rakefile +12 -0
- data/examples/README.md +321 -0
- data/examples/best_practices.md +401 -0
- data/examples/configurations/basic_config.rb +24 -0
- data/examples/configurations/ci_config.rb +35 -0
- data/examples/configurations/conservative_config.rb +32 -0
- data/examples/configurations/development_config.rb +37 -0
- data/examples/configurations/performance_focused_config.rb +38 -0
- data/examples/output_formatter_demo.rb +67 -0
- data/examples/sample_outputs/console_output_high_confidence.txt +27 -0
- data/examples/sample_outputs/console_output_medium_confidence.txt +27 -0
- data/examples/sample_outputs/console_output_no_action.txt +27 -0
- data/examples/sample_outputs/console_output_risk_detected.txt +27 -0
- data/examples/sample_outputs/json_output_high_confidence.json +108 -0
- data/examples/sample_outputs/json_output_no_action.json +108 -0
- data/examples/workflows/basic_workflow.md +159 -0
- data/examples/workflows/ci_integration.md +372 -0
- data/exe/spec_scout +7 -0
- data/lib/spec_scout/agent_result.rb +44 -0
- data/lib/spec_scout/agents/database_agent.rb +113 -0
- data/lib/spec_scout/agents/factory_agent.rb +179 -0
- data/lib/spec_scout/agents/intent_agent.rb +223 -0
- data/lib/spec_scout/agents/risk_agent.rb +290 -0
- data/lib/spec_scout/base_agent.rb +72 -0
- data/lib/spec_scout/cli.rb +158 -0
- data/lib/spec_scout/configuration.rb +162 -0
- data/lib/spec_scout/consensus_engine.rb +535 -0
- data/lib/spec_scout/enforcement_handler.rb +182 -0
- data/lib/spec_scout/output_formatter.rb +307 -0
- data/lib/spec_scout/profile_data.rb +37 -0
- data/lib/spec_scout/profile_normalizer.rb +238 -0
- data/lib/spec_scout/recommendation.rb +62 -0
- data/lib/spec_scout/safety_validator.rb +127 -0
- data/lib/spec_scout/spec_scout.rb +519 -0
- data/lib/spec_scout/testprof_integration.rb +206 -0
- data/lib/spec_scout/version.rb +5 -0
- data/lib/spec_scout.rb +43 -0
- metadata +166 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_prof'
|
|
4
|
+
|
|
5
|
+
module SpecScout
|
|
6
|
+
# Handles TestProf integration, execution, and data extraction
|
|
7
|
+
class TestProfIntegration
|
|
8
|
+
class TestProfError < StandardError; end
|
|
9
|
+
|
|
10
|
+
def initialize(config = nil)
|
|
11
|
+
@config = config || ::SpecScout.configuration
|
|
12
|
+
@enabled = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Enable and execute TestProf profiling
|
|
16
|
+
def execute_profiling(_spec_location = nil)
|
|
17
|
+
return nil unless @config.use_test_prof
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
enable_testprof_profilers
|
|
21
|
+
@enabled = true
|
|
22
|
+
|
|
23
|
+
# Extract profile data immediately after enabling
|
|
24
|
+
extract_profile_data
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
raise TestProfError, "Failed to enable TestProf: #{e.message}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Extract structured profile data from TestProf results
|
|
31
|
+
def extract_profile_data
|
|
32
|
+
return {} unless @enabled
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
{
|
|
36
|
+
factory_prof: extract_factory_prof_data,
|
|
37
|
+
event_prof: extract_event_prof_data,
|
|
38
|
+
db_queries: extract_db_query_data
|
|
39
|
+
}
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
raise TestProfError, "Failed to extract TestProf data: #{e.message}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if TestProf is available and properly configured
|
|
46
|
+
def testprof_available?
|
|
47
|
+
defined?(TestProf) && TestProf.respond_to?(:config)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def enable_testprof_profilers
|
|
53
|
+
raise TestProfError, 'TestProf not available' unless testprof_available?
|
|
54
|
+
|
|
55
|
+
# Enable FactoryProf
|
|
56
|
+
TestProf::FactoryProf.init if defined?(TestProf::FactoryProf)
|
|
57
|
+
|
|
58
|
+
# Configure TestProf settings
|
|
59
|
+
configure_testprof_settings
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def configure_testprof_settings
|
|
63
|
+
return unless defined?(TestProf)
|
|
64
|
+
|
|
65
|
+
# Set up basic TestProf configuration
|
|
66
|
+
TestProf.configure do |config|
|
|
67
|
+
config.output_dir = 'tmp/test_prof'
|
|
68
|
+
config.timestamps = true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def extract_factory_prof_data
|
|
73
|
+
return {} unless defined?(TestProf::FactoryProf)
|
|
74
|
+
|
|
75
|
+
# Extract FactoryProf results if available
|
|
76
|
+
if TestProf::FactoryProf.respond_to?(:stats)
|
|
77
|
+
stats = TestProf::FactoryProf.stats
|
|
78
|
+
return {} unless stats
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
total_count: stats.values.sum { |s| s[:total] || 0 },
|
|
82
|
+
total_time: stats.values.sum { |s| s[:time] || 0.0 },
|
|
83
|
+
stats: extract_factory_stats_from_hash(stats)
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
{}
|
|
87
|
+
end
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
# Log error but don't fail the entire extraction
|
|
90
|
+
{ error: "FactoryProf extraction failed: #{e.message}" }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def extract_factory_stats_from_hash(stats)
|
|
94
|
+
return {} unless stats.is_a?(Hash)
|
|
95
|
+
|
|
96
|
+
result = {}
|
|
97
|
+
stats.each do |factory_name, factory_data|
|
|
98
|
+
result[factory_name.to_sym] = {
|
|
99
|
+
count: factory_data[:total] || factory_data[:count] || 0,
|
|
100
|
+
time: factory_data[:time] || 0.0,
|
|
101
|
+
strategy: detect_factory_strategy(factory_data)
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
result
|
|
105
|
+
rescue StandardError
|
|
106
|
+
{}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def detect_factory_strategy(factory_data)
|
|
110
|
+
# Try to detect if factory used create, build, or build_stubbed
|
|
111
|
+
# This is a simplified detection - real implementation would need
|
|
112
|
+
# more sophisticated analysis of TestProf data
|
|
113
|
+
if factory_data[:create_count]&.positive?
|
|
114
|
+
:create
|
|
115
|
+
elsif factory_data[:build_count]&.positive?
|
|
116
|
+
:build
|
|
117
|
+
else
|
|
118
|
+
:unknown
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def extract_event_prof_data
|
|
123
|
+
return {} unless defined?(TestProf::EventProf)
|
|
124
|
+
|
|
125
|
+
# EventProf doesn't have a simple results API like FactoryProf
|
|
126
|
+
# Return empty data for now - this would need to be implemented
|
|
127
|
+
# based on how EventProf actually stores its results
|
|
128
|
+
{}
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
# Log error but don't fail the entire extraction
|
|
131
|
+
{ error: "EventProf extraction failed: #{e.message}" }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def extract_event_stats(results)
|
|
135
|
+
return {} unless results.respond_to?(:each)
|
|
136
|
+
|
|
137
|
+
events = {}
|
|
138
|
+
results.each do |event_name, event_data|
|
|
139
|
+
events[event_name.to_sym] = {
|
|
140
|
+
count: event_data[:count] || 0,
|
|
141
|
+
time: event_data[:time] || 0.0,
|
|
142
|
+
examples: event_data[:examples] || []
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
events
|
|
146
|
+
rescue StandardError
|
|
147
|
+
{}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def extract_db_query_data
|
|
151
|
+
# Extract database query information
|
|
152
|
+
# This would typically come from EventProf sql.active_record events
|
|
153
|
+
# or other TestProf database profiling features
|
|
154
|
+
|
|
155
|
+
db_data = {
|
|
156
|
+
total_queries: 0,
|
|
157
|
+
inserts: 0,
|
|
158
|
+
selects: 0,
|
|
159
|
+
updates: 0,
|
|
160
|
+
deletes: 0
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Try to get SQL event data from EventProf
|
|
164
|
+
event_data = extract_event_prof_data
|
|
165
|
+
if event_data[:events] && event_data[:events][:'sql.active_record']
|
|
166
|
+
sql_events = event_data[:events][:'sql.active_record']
|
|
167
|
+
db_data[:total_queries] = sql_events[:count] || 0
|
|
168
|
+
|
|
169
|
+
# Parse examples to categorize query types
|
|
170
|
+
categorize_sql_queries(sql_events[:examples], db_data) if sql_events[:examples]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
db_data
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
# Return default structure on error
|
|
176
|
+
{
|
|
177
|
+
total_queries: 0,
|
|
178
|
+
inserts: 0,
|
|
179
|
+
selects: 0,
|
|
180
|
+
updates: 0,
|
|
181
|
+
deletes: 0,
|
|
182
|
+
error: "DB query extraction failed: #{e.message}"
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def categorize_sql_queries(examples, db_data)
|
|
187
|
+
examples.each do |example|
|
|
188
|
+
next unless example.is_a?(Hash) && example[:sql]
|
|
189
|
+
|
|
190
|
+
sql = example[:sql].to_s.upcase
|
|
191
|
+
case sql
|
|
192
|
+
when /^INSERT/
|
|
193
|
+
db_data[:inserts] += 1
|
|
194
|
+
when /^SELECT/
|
|
195
|
+
db_data[:selects] += 1
|
|
196
|
+
when /^UPDATE/
|
|
197
|
+
db_data[:updates] += 1
|
|
198
|
+
when /^DELETE/
|
|
199
|
+
db_data[:deletes] += 1
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
rescue StandardError
|
|
203
|
+
# Ignore categorization errors
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
data/lib/spec_scout.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'spec_scout/version'
|
|
4
|
+
require_relative 'spec_scout/profile_data'
|
|
5
|
+
require_relative 'spec_scout/agent_result'
|
|
6
|
+
require_relative 'spec_scout/recommendation'
|
|
7
|
+
require_relative 'spec_scout/base_agent'
|
|
8
|
+
require_relative 'spec_scout/configuration'
|
|
9
|
+
require_relative 'spec_scout/safety_validator'
|
|
10
|
+
require_relative 'spec_scout/enforcement_handler'
|
|
11
|
+
require_relative 'spec_scout/testprof_integration'
|
|
12
|
+
require_relative 'spec_scout/profile_normalizer'
|
|
13
|
+
require_relative 'spec_scout/consensus_engine'
|
|
14
|
+
require_relative 'spec_scout/output_formatter'
|
|
15
|
+
|
|
16
|
+
# Agents
|
|
17
|
+
require_relative 'spec_scout/agents/database_agent'
|
|
18
|
+
require_relative 'spec_scout/agents/factory_agent'
|
|
19
|
+
require_relative 'spec_scout/agents/intent_agent'
|
|
20
|
+
require_relative 'spec_scout/agents/risk_agent'
|
|
21
|
+
|
|
22
|
+
# Main orchestration class
|
|
23
|
+
require_relative 'spec_scout/spec_scout'
|
|
24
|
+
require_relative 'spec_scout/cli'
|
|
25
|
+
|
|
26
|
+
# Main module for Spec Scout, a tool for analyzing and optimizing test suite database and factory usage.
|
|
27
|
+
module SpecScout
|
|
28
|
+
class Error < StandardError; end
|
|
29
|
+
|
|
30
|
+
# Main entry point for Spec Scout functionality
|
|
31
|
+
def self.configure
|
|
32
|
+
yield(configuration) if block_given?
|
|
33
|
+
configuration
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.configuration
|
|
37
|
+
@configuration ||= Configuration.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.reset_configuration!
|
|
41
|
+
@configuration = nil
|
|
42
|
+
end
|
|
43
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: spec_scout
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ajeet Kumar
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: test-prof
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: prop_check
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.18'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.18'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.12'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.12'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.21'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.21'
|
|
83
|
+
description: Spec Scout transforms TestProf profiling data into actionable optimization
|
|
84
|
+
recommendations using specialized agents
|
|
85
|
+
email:
|
|
86
|
+
- akv1087@gmail.com
|
|
87
|
+
executables:
|
|
88
|
+
- spec_scout
|
|
89
|
+
extensions: []
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- ".idea/.gitignore"
|
|
93
|
+
- ".idea/Projects.iml"
|
|
94
|
+
- ".idea/copilot.data.migration.ask2agent.xml"
|
|
95
|
+
- ".idea/modules.xml"
|
|
96
|
+
- ".idea/vcs.xml"
|
|
97
|
+
- ".rspec_status"
|
|
98
|
+
- Gemfile
|
|
99
|
+
- Gemfile.lock
|
|
100
|
+
- LICENSE
|
|
101
|
+
- README.md
|
|
102
|
+
- Rakefile
|
|
103
|
+
- examples/README.md
|
|
104
|
+
- examples/best_practices.md
|
|
105
|
+
- examples/configurations/basic_config.rb
|
|
106
|
+
- examples/configurations/ci_config.rb
|
|
107
|
+
- examples/configurations/conservative_config.rb
|
|
108
|
+
- examples/configurations/development_config.rb
|
|
109
|
+
- examples/configurations/performance_focused_config.rb
|
|
110
|
+
- examples/output_formatter_demo.rb
|
|
111
|
+
- examples/sample_outputs/console_output_high_confidence.txt
|
|
112
|
+
- examples/sample_outputs/console_output_medium_confidence.txt
|
|
113
|
+
- examples/sample_outputs/console_output_no_action.txt
|
|
114
|
+
- examples/sample_outputs/console_output_risk_detected.txt
|
|
115
|
+
- examples/sample_outputs/json_output_high_confidence.json
|
|
116
|
+
- examples/sample_outputs/json_output_no_action.json
|
|
117
|
+
- examples/workflows/basic_workflow.md
|
|
118
|
+
- examples/workflows/ci_integration.md
|
|
119
|
+
- exe/spec_scout
|
|
120
|
+
- lib/spec_scout.rb
|
|
121
|
+
- lib/spec_scout/agent_result.rb
|
|
122
|
+
- lib/spec_scout/agents/database_agent.rb
|
|
123
|
+
- lib/spec_scout/agents/factory_agent.rb
|
|
124
|
+
- lib/spec_scout/agents/intent_agent.rb
|
|
125
|
+
- lib/spec_scout/agents/risk_agent.rb
|
|
126
|
+
- lib/spec_scout/base_agent.rb
|
|
127
|
+
- lib/spec_scout/cli.rb
|
|
128
|
+
- lib/spec_scout/configuration.rb
|
|
129
|
+
- lib/spec_scout/consensus_engine.rb
|
|
130
|
+
- lib/spec_scout/enforcement_handler.rb
|
|
131
|
+
- lib/spec_scout/output_formatter.rb
|
|
132
|
+
- lib/spec_scout/profile_data.rb
|
|
133
|
+
- lib/spec_scout/profile_normalizer.rb
|
|
134
|
+
- lib/spec_scout/recommendation.rb
|
|
135
|
+
- lib/spec_scout/safety_validator.rb
|
|
136
|
+
- lib/spec_scout/spec_scout.rb
|
|
137
|
+
- lib/spec_scout/testprof_integration.rb
|
|
138
|
+
- lib/spec_scout/version.rb
|
|
139
|
+
homepage: https://github.com/ajeet-g2/spec-scout
|
|
140
|
+
licenses:
|
|
141
|
+
- MIT
|
|
142
|
+
metadata:
|
|
143
|
+
allowed_push_host: https://rubygems.org
|
|
144
|
+
homepage_uri: https://github.com/ajeet-g2/spec-scout
|
|
145
|
+
source_code_uri: https://github.com/ajeet-g2/spec-scout
|
|
146
|
+
changelog_uri: https://github.com/ajeet-g2/spec-scout/blob/main/CHANGELOG.md
|
|
147
|
+
post_install_message:
|
|
148
|
+
rdoc_options: []
|
|
149
|
+
require_paths:
|
|
150
|
+
- lib
|
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: 2.7.0
|
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '0'
|
|
161
|
+
requirements: []
|
|
162
|
+
rubygems_version: 3.4.19
|
|
163
|
+
signing_key:
|
|
164
|
+
specification_version: 4
|
|
165
|
+
summary: Intelligent test optimization advisor built on TestProf
|
|
166
|
+
test_files: []
|