igata 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78199aa96fa495664cb461c46bbabdce3afa9a21f3083e5f49e41cb47e66f909
4
- data.tar.gz: e2a0d35b071882c6b28ea13d1b88f846e3bb74adcf1ca92d7700aa7be635ee44
3
+ metadata.gz: 67e0f333dc1a9f2e7088ca132ab093f1d94c54da84fb74f68875e6712fd6914c
4
+ data.tar.gz: b2f6a8ea974699fcedfe03b5d67fa53af10a8973d38141fca304fa08b015ad16
5
5
  SHA512:
6
- metadata.gz: 4c5b75401c77c515dfdcb5968d44c7c4e8569ed10ebc03c5e7a7a8ab7e8225aa086cabcb66c99c7519f3c35fc5fb9f2789409d56d5d4961995c07ae79192df94
7
- data.tar.gz: eb80ea990597cda4110a8b7e431d41d6c19c8af8bf5387e2e8ad270ea273e1b39f913107069ec8da2c8b836dbbe1efe8fe885aae9fc07afb26a53568efb49f26
6
+ metadata.gz: 191413d88a85822e823cd5a858b5e37b22c435debf020994184375aa0c34f8257285f0c8b85483f9be7deaf22aec045ee8146607a92d1e81d91c16e57e4577f9
7
+ data.tar.gz: 3daba51294a26df61cab417a5885a35e0d9a8cc63e7d584a5f379ce9ff20dc65e59247bd68e519feb049faee0a6affc22f46dd17ac14326f4383e283d581f060
data/CHANGELOG.md CHANGED
@@ -1,5 +1,98 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-11-29
4
+
5
+ ### Added
6
+
7
+ - Method argument information in generated tests:
8
+ - Test methods now display argument information for each method
9
+ - Shows argument names, types, and default values
10
+ - Supports all argument types: required, optional, keyword, required keyword, variable length (*args), keyword variable length (**kwargs), and block (&block)
11
+ - Arguments are displayed at the top of test methods before other analysis information
12
+ - Example: `# Arguments: - name (required), - age (optional, default: 18)`
13
+ - Fully compatible with all formatters (Minitest, RSpec, Minitest Spec)
14
+ - Integration test tasks for real-world Rails applications
15
+ - `rake integration:setup`: Clone test repositories (Rails, Discourse, Mastodon, GitLab)
16
+ - `rake integration:test`: Run Igata against all Ruby files in repositories
17
+ - `rake integration:clean`: Remove cloned repositories
18
+ - `rake integration:update`: Update cloned repositories
19
+ - Minitest Spec formatter support
20
+ - New `-f minitest_spec` option for generating Minitest Spec style tests
21
+ - Uses Minitest's built-in RSpec-style DSL (minitest/spec)
22
+ - Generates `describe`/`it` blocks instead of class-based tests
23
+ - Fully compatible with all existing analysis features (branches, comparisons, exceptions, boundary values)
24
+ - Added exception and error handling analysis:
25
+ - `Extractors::ExceptionAnalyzer`: Detects `raise` statements and `rescue` clauses in methods
26
+ - `Values::ExceptionInfo`: Value object for exception information (type, class, message, context)
27
+ - Generates test comments showing:
28
+ - Exceptions raised: Shows exception class and message (e.g., `ArgumentError ("Invalid amount")`)
29
+ - Exceptions rescued: Shows caught exception classes (e.g., `PaymentError`)
30
+ - Supports both Minitest and RSpec formats
31
+ - Added boundary value test case suggestions:
32
+ - `Extractors::BoundaryValueGenerator`: Automatically generates boundary value suggestions from comparison operators
33
+ - `Values::BoundaryValueInfo`: Value object for boundary value information (comparison, test_values, description)
34
+ - Generates test comments showing suggested test values for various types:
35
+ - Numeric values: `age >= 18` → Test with `[17 (below), 18 (boundary), 19 (above)]`
36
+ - String literals: `name == "Alice"` → Test with `["Alice", "different_string"]`
37
+ - Nil values: `value == nil` → Test with `[nil, "some_value"]`
38
+ - Boolean values: `flag == true` → Test with `[true, false]`
39
+ - Symbol values: `status == :active` → Test with `[":active", ":other_symbol"]`
40
+ - Supports all comparison operators: `>=`, `>`, `<=`, `<`, `==`, `!=`
41
+ - Supports both Minitest and RSpec formats
42
+ - Skips boundary value generation for variable references and method calls
43
+
44
+ ### Changed
45
+
46
+ - CLI now supports three formatters: `minitest` (default), `rspec`, and `minitest_spec`
47
+ - Updated `Values::MethodInfo` to include `exceptions` field (default: [])
48
+ - Updated `Values::MethodInfo` to include `boundary_values` field (default: [])
49
+ - Updated `Extractors::MethodNames` to automatically run `BoundaryValueGenerator` for each method
50
+ - Updated Minitest and RSpec formatters to include exception information in generated test comments
51
+ - Updated Minitest and RSpec formatters to include boundary value suggestions in generated test comments
52
+ - Updated Kanayago dependency from ~> 0.3.0 to ~> 0.6.1
53
+ - `Kanayago.parse` now returns `Kanayago::ParseResult` instead of AST node directly
54
+ - Updated all test cases to use `.ast` accessor
55
+ - This enables future enhancements:
56
+ - Syntax error handling for invalid Ruby code
57
+ - Method argument extraction using script_lines
58
+ - see: https://github.com/S-H-GAMELINKS/kanayago/blob/master/CHANGELOG.md#060
59
+
60
+ ### Documentation
61
+
62
+ - Added Minitest Spec examples to README.md
63
+ - Added Minitest Spec usage guide to QUICKSTART.md
64
+ - Updated USAGE.md with Minitest Spec details
65
+
66
+ ### Fixed
67
+
68
+ - Fixed BlockNode and BeginNode handling for real-world Ruby code
69
+ - Fixed BlockNode handling: When `require` statements or constants appear before class definitions, the AST creates a BlockNode wrapper
70
+ - Added `find_class_node` method in `Extractors::ConstantPath` to search for ClassNode within BlockNode
71
+ - Example: `backup_service.rb` with `require 'zip'` at the top
72
+ - Fixed BeginNode handling: Empty classes or classes with special structures create BeginNode that lack the `find` method
73
+ - Added `respond_to?` checks in `find_deepest_class_node` to safely handle these cases
74
+ - Returns `nil` for BeginNode structures
75
+ - Added test cases for both scenarios (`class_with_require` and `class_with_no_methods`) to prevent regression
76
+ - Verified with all 69 Mastodon service classes (100% success rate)
77
+ - Fixed StringNode value extraction to use `ptr` instead of `val` in `BranchAnalyzer` and `ComparisonAnalyzer`
78
+ - Kanayago 0.4.0 changed the API: string values are now stored in `ptr` field instead of `val`
79
+ - Added integration tests for string literal comparisons and branches to prevent regression
80
+ - Fixed `undefined method 'cpath' for nil` error when processing files without class/module definitions
81
+ - Updated `find_class_node` to return `nil` when no ClassNode or ModuleNode is found
82
+ - Added guard clause in `extract` method to handle `nil` class nodes
83
+ - Updated `Igata#generate` to return empty string when no class/module is found
84
+ - This allows Igata to gracefully handle configuration files, scripts, and other Ruby files without class definitions
85
+
86
+ ## [0.2.1] - 2025-10-26
87
+
88
+ ### Added
89
+
90
+ - Added Kanayago (~> 0.3.0) as runtime dependency to ensure automatic installation
91
+
92
+ ### Fixed
93
+
94
+ - Fixed LoadError when Kanayago is not manually installed by adding it as gemspec dependency
95
+
3
96
  ## [0.2.0] - 2025-10-26
4
97
 
5
98
  ### Added
data/QUICKSTART.md CHANGED
@@ -111,6 +111,43 @@ RSpec.describe User do
111
111
  end
112
112
  ```
113
113
 
114
+ ### 4. Generate Minitest Spec
115
+
116
+ ```bash
117
+ igata user.rb -f minitest_spec > test/user_spec.rb
118
+ ```
119
+
120
+ Output:
121
+
122
+ ```ruby
123
+ # frozen_string_literal: true
124
+
125
+ require "test_helper"
126
+ require "minitest/spec"
127
+
128
+ describe User do
129
+ describe "#initialize" do
130
+ it "works correctly" do
131
+ skip "Not implemented yet"
132
+ end
133
+ end
134
+
135
+ describe "#adult?" do
136
+ # Comparisons: >= (@age >= 18)
137
+ it "works correctly" do
138
+ skip "Not implemented yet"
139
+ end
140
+ end
141
+
142
+ describe "#greeting" do
143
+ # Branches: if (@name)
144
+ it "works correctly" do
145
+ skip "Not implemented yet"
146
+ end
147
+ end
148
+ end
149
+ ```
150
+
114
151
  ## Common Usage Patterns
115
152
 
116
153
  ### Generate from stdin
data/README.md CHANGED
@@ -36,10 +36,19 @@ bundle exec igata lib/user.rb -f rspec > spec/user_spec.rb
36
36
  bundle exec igata lib/user.rb --formatter rspec > spec/user_spec.rb
37
37
  ```
38
38
 
39
+ Generate Minitest Spec tests:
40
+
41
+ ```bash
42
+ bundle exec igata lib/user.rb -f minitest_spec > test/user_spec.rb
43
+ # or
44
+ bundle exec igata lib/user.rb --formatter minitest_spec > test/user_spec.rb
45
+ ```
46
+
39
47
  ### Supported Formatters
40
48
 
41
- - `minitest` (default): Generates Minitest-style tests
49
+ - `minitest` (default): Generates Minitest-style tests (class-based)
42
50
  - `rspec`: Generates RSpec-style tests
51
+ - `minitest_spec`: Generates Minitest Spec-style tests (RSpec-style DSL with Minitest)
43
52
 
44
53
  ### Example
45
54
 
@@ -67,11 +76,16 @@ require "test_helper"
67
76
 
68
77
  class UserTest < Minitest::Test
69
78
  def test_initialize
79
+ # Arguments:
80
+ # - name (required)
81
+ # - age (required)
70
82
  skip "Not implemented yet"
71
83
  end
72
84
 
73
85
  def test_adult?
74
86
  # Comparisons: >= (@age >= 18)
87
+ # Boundary value suggestions:
88
+ # @age >= 18: [17 (below), 18 (boundary), 19 (above)]
75
89
  skip "Not implemented yet"
76
90
  end
77
91
  end
@@ -86,6 +100,9 @@ require "spec_helper"
86
100
 
87
101
  RSpec.describe User do
88
102
  describe "#initialize" do
103
+ # Arguments:
104
+ # - name (required)
105
+ # - age (required)
89
106
  it "works correctly" do
90
107
  pending "Not implemented yet"
91
108
  end
@@ -93,6 +110,8 @@ RSpec.describe User do
93
110
 
94
111
  describe "#adult?" do
95
112
  # Comparisons: >= (@age >= 18)
113
+ # Boundary value suggestions:
114
+ # @age >= 18: [17 (below), 18 (boundary), 19 (above)]
96
115
  it "works correctly" do
97
116
  pending "Not implemented yet"
98
117
  end
@@ -100,12 +119,96 @@ RSpec.describe User do
100
119
  end
101
120
  ```
102
121
 
122
+ Minitest Spec output:
123
+
124
+ ```ruby
125
+ # frozen_string_literal: true
126
+
127
+ require "test_helper"
128
+ require "minitest/spec"
129
+
130
+ describe User do
131
+ describe "#initialize" do
132
+ # Arguments:
133
+ # - name (required)
134
+ # - age (required)
135
+ it "works correctly" do
136
+ skip "Not implemented yet"
137
+ end
138
+ end
139
+
140
+ describe "#adult?" do
141
+ # Comparisons: >= (@age >= 18)
142
+ # Boundary value suggestions:
143
+ # @age >= 18: [17 (below), 18 (boundary), 19 (above)]
144
+ it "works correctly" do
145
+ skip "Not implemented yet"
146
+ end
147
+ end
148
+ end
149
+ ```
150
+
151
+ ### Exception Handling Detection
152
+
153
+ Igata detects exception handling patterns and generates helpful test comments:
154
+
155
+ ```ruby
156
+ class PaymentProcessor
157
+ def process_payment(amount)
158
+ raise ArgumentError, "Invalid amount" if amount <= 0
159
+
160
+ charge_card(amount)
161
+ rescue PaymentError => e
162
+ log_error(e)
163
+ false
164
+ end
165
+ end
166
+ ```
167
+
168
+ Generated Minitest output:
169
+
170
+ ```ruby
171
+ class PaymentProcessorTest < Minitest::Test
172
+ def test_process_payment
173
+ # Branches: if (amount <= 0)
174
+ # Comparisons: <= (amount <= 0)
175
+ # Exceptions raised: ArgumentError ("Invalid amount")
176
+ # Exceptions rescued: PaymentError
177
+ skip "Not implemented yet"
178
+ end
179
+ end
180
+ ```
181
+
182
+ ## Features
183
+
184
+ - ✅ **Argument Analysis**: Displays method argument information (types, default values)
185
+ - ✅ **Branch Analysis**: Detects `if`, `elsif`, `else`, `unless`, and `case` statements
186
+ - ✅ **Comparison Analysis**: Detects comparison operators (`>=`, `<=`, `>`, `<`, `==`, `!=`)
187
+ - ✅ **Boundary Value Suggestions**: Automatically suggests boundary test values for comparisons
188
+ - ✅ **Exception Analysis**: Detects `raise` statements and `rescue` clauses
189
+ - ✅ **Multiple Formatters**: Supports Minitest, RSpec, and Minitest Spec test generation
190
+ - ✅ **AST-based**: Uses Kanayago parser for accurate Ruby code analysis
191
+
103
192
  ## Development
104
193
 
105
194
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
106
195
 
107
196
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
108
197
 
198
+ ### Integration Tests
199
+
200
+ Run integration tests against real-world Rails applications (Rails, Discourse, Mastodon, GitLab):
201
+
202
+ ```bash
203
+ # Run integration tests (automatically clones repositories on first run)
204
+ bundle exec rake integration:test
205
+
206
+ # Manually setup/update/clean repositories
207
+ bundle exec rake integration:setup # Clone repositories
208
+ bundle exec rake integration:update # Update repositories
209
+ bundle exec rake integration:clean # Remove repositories
210
+ ```
211
+
109
212
  ## Contributing
110
213
 
111
214
  Bug reports and pull requests are welcome on GitHub at https://github.com/S-H-GAMELINKS/igata.
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
+ require "fileutils"
5
6
 
6
7
  Minitest::TestTask.create
7
8
 
@@ -10,3 +11,137 @@ require "rubocop/rake_task"
10
11
  RuboCop::RakeTask.new
11
12
 
12
13
  task default: %i[test rubocop]
14
+
15
+ INTEGRATION_TEST_TARGET_REPOS_DIR = File.expand_path("tmp/integration_repos", __dir__)
16
+ INTEGRATION_TEST_TARGET_REPOSITORIES = {
17
+ "rails" => {
18
+ url: "https://github.com/rails/rails.git",
19
+ branch: "main",
20
+ depth: 1
21
+ },
22
+ "discourse" => {
23
+ url: "https://github.com/discourse/discourse.git",
24
+ branch: "main",
25
+ depth: 1
26
+ },
27
+ "mastodon" => {
28
+ url: "https://github.com/mastodon/mastodon.git",
29
+ branch: "main",
30
+ depth: 1
31
+ },
32
+ "gitlab" => {
33
+ url: "https://gitlab.com/gitlab-org/gitlab.git",
34
+ branch: "master",
35
+ depth: 1
36
+ }
37
+ }.freeze
38
+
39
+ namespace :integration do # rubocop:disable Metrics/BlockLength
40
+ desc "Setup integration test repositories"
41
+ task :setup do
42
+ FileUtils.mkdir_p(INTEGRATION_TEST_TARGET_REPOS_DIR)
43
+
44
+ INTEGRATION_TEST_TARGET_REPOSITORIES.each do |name, config|
45
+ repo_path = File.join(INTEGRATION_TEST_TARGET_REPOS_DIR, name)
46
+
47
+ if File.directory?(repo_path)
48
+ puts "Repository #{name} already exists at #{repo_path}"
49
+ next
50
+ end
51
+
52
+ puts "Cloning #{name} from #{config[:url]}..."
53
+ system("git clone --depth #{config[:depth]} --branch #{config[:branch]} #{config[:url]} #{repo_path}")
54
+
55
+ if $?.success? # rubocop:disable Style/SpecialGlobalVars
56
+ puts "Successfully cloned #{name}"
57
+ else
58
+ puts "Failed to clone #{name}"
59
+ end
60
+ end
61
+
62
+ puts "\nIntegration test repositories setup complete!"
63
+ end
64
+
65
+ desc "Clean integration test repositories"
66
+ task :clean do
67
+ if File.directory?(INTEGRATION_TEST_TARGET_REPOS_DIR)
68
+ puts "Removing integration test repositories at #{INTEGRATION_TEST_TARGET_REPOS_DIR}..."
69
+ FileUtils.rm_rf(INTEGRATION_TEST_TARGET_REPOS_DIR)
70
+ puts "Done!"
71
+ else
72
+ puts "No integration test repositories found"
73
+ end
74
+ end
75
+
76
+ desc "Update integration test repositories"
77
+ task :update do
78
+ unless File.directory?(INTEGRATION_TEST_TARGET_REPOS_DIR)
79
+ puts "No repositories found. Run 'rake integration:setup' first."
80
+ next
81
+ end
82
+
83
+ INTEGRATION_TEST_TARGET_REPOSITORIES.each_key do |name|
84
+ repo_path = File.join(INTEGRATION_TEST_TARGET_REPOS_DIR, name)
85
+
86
+ unless File.directory?(repo_path)
87
+ puts "Repository #{name} not found, skipping..."
88
+ next
89
+ end
90
+
91
+ puts "Updating #{name}..."
92
+ Dir.chdir(repo_path) do
93
+ system("git pull")
94
+ end
95
+ end
96
+
97
+ puts "\nIntegration test repositories updated!"
98
+ end
99
+
100
+ desc "Run integration tests against real-world repositories"
101
+ task test: :setup do # rubocop:disable Metrics/BlockLength
102
+ require_relative "lib/igata"
103
+
104
+ success = true
105
+
106
+ INTEGRATION_TEST_TARGET_REPOSITORIES.each_key do |name| # rubocop:disable Metrics/BlockLength
107
+ repo_path = File.join(INTEGRATION_TEST_TARGET_REPOS_DIR, name)
108
+
109
+ unless File.directory?(repo_path)
110
+ puts "Repository #{name} not found, skipping..."
111
+ next
112
+ end
113
+
114
+ puts "\n#{"=" * 60}"
115
+ puts "Testing #{name}..."
116
+ puts "=" * 60
117
+
118
+ ruby_files = Dir.glob(File.join(repo_path, "**", "*.rb"))
119
+ puts "Found #{ruby_files.size} Ruby files"
120
+
121
+ failed_files = []
122
+ ruby_files.each_with_index do |file, index|
123
+ print "\r Processing #{index + 1}/#{ruby_files.size}: #{File.basename(file)}".ljust(80)
124
+
125
+ begin
126
+ source = File.read(file)
127
+ Igata.new(source).generate
128
+ rescue StandardError => e
129
+ failed_files << { file: file, error: e.message }
130
+ end
131
+ end
132
+
133
+ puts "\n Completed: #{ruby_files.size - failed_files.size}/#{ruby_files.size} files passed"
134
+
135
+ next if failed_files.empty?
136
+
137
+ puts " Failed files:"
138
+ failed_files.first(10).each do |f|
139
+ puts " - #{f[:file]}: #{f[:error]}"
140
+ end
141
+ puts " ... and #{failed_files.size - 10} more" if failed_files.size > 10
142
+ success = false
143
+ end
144
+
145
+ exit(1) unless success
146
+ end
147
+ end