tng 0.4.4 → 0.4.5

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: 6eddc7e29e797e24c60704b80241673219df08aeaede138116921e5b971904d7
4
- data.tar.gz: f6376250f73f39ae016cb90f82eaf7f67d1b396eafe92a3718f9e82d4a560322
3
+ metadata.gz: 70161861ae22db154e31792a86414ad76d14d56d6a5c7ea14de126e26bf2e7a5
4
+ data.tar.gz: ee5bc28ae08666fa789ff0ca21c67735340c01e850b11afcbf34e80fa0d80d3a
5
5
  SHA512:
6
- metadata.gz: a835a99f7f8bd93b3cd50d22f4e100aa5f7ed06c67588806b99c86cdaf5a08c4e5bcda43f4234bc1c1491a4c4e2cde3f457d6b83a348224bfec6c820eb28c81a
7
- data.tar.gz: 0d2a70f92c28099725e8e2de31a467b6ef1cbe6e3263a788a09ba2408e617bb0efce643eb537d8f9d56ad01656dd2f1a06e8c1daf7b8ba60ba6a90b02391c97d
6
+ metadata.gz: ebd0fb7dbcbbb795810ae2da51ef4333c0a3e2d226c0cb5f5c5f3de7a965899ef099f7e0874c12f1a040c6f47a8db220b3658d12d5cae8b86b4827d4ade12691
7
+ data.tar.gz: 87e2aee29fbd86bb25aa2519d1e602b96bfc45a7d9a63ffd3e91697a9dae5eb4503bdf30c0b345b8de7641b91ee947b8c6b2b48a5b3da3f78a03024bcb636e93
data/bin/tng CHANGED
@@ -64,6 +64,11 @@ class CLI
64
64
  desc "Run in audit mode (find issues and behaviours instead of generating tests)"
65
65
  end
66
66
 
67
+ flag :trace do
68
+ long "--trace"
69
+ desc "Generate and visualize a symbolic trace"
70
+ end
71
+
67
72
  flag :json do
68
73
  long "--json"
69
74
  desc "Output results in JSON format"
@@ -116,6 +121,8 @@ class CLI
116
121
 
117
122
  if params[:fix]
118
123
  handle_fix_command
124
+ elsif params[:trace] && params[:file]
125
+ run_direct_trace
119
126
  elsif params[:file]
120
127
  run_direct_generation
121
128
  else
@@ -149,6 +156,8 @@ class CLI
149
156
  normalized << "--method=#{::Regexp.last_match(2)}"
150
157
  when /^(?:--)?(audit|a)$/
151
158
  normalized << "--audit"
159
+ when /^(?:--)?(trace)$/
160
+ normalized << "--trace"
152
161
  when /^(?:--)?(json)$/
153
162
  normalized << "--json"
154
163
  when /^(?:--)?(fix|x)$/
@@ -208,6 +217,8 @@ class CLI
208
217
  handle_test_generation
209
218
  when "audit"
210
219
  handle_audit_method
220
+ when "trace"
221
+ handle_trace_method
211
222
  when "stats"
212
223
  user_stats
213
224
  when "about"
@@ -1069,7 +1080,254 @@ class CLI
1069
1080
  orchestrator = Tng::Services::FixOrchestrator.new(@pastel, @http_client)
1070
1081
  orchestrator.run(file_path)
1071
1082
  end
1083
+
1084
+ def handle_trace_method
1085
+ choice = @go_ui.show_test_type_menu("trace")
1086
+
1087
+ case choice
1088
+ when "controller"
1089
+ trace_controller_method
1090
+ when "model"
1091
+ trace_model_method
1092
+ when "service"
1093
+ trace_service_method
1094
+ when "other"
1095
+ trace_other_method
1096
+ end
1097
+ end
1098
+
1099
+ def trace_controller_method
1100
+ select_controller_and_method("Trace") do |controller, method_info|
1101
+ run_trace_for_method(controller, method_info)
1102
+ end
1103
+ end
1104
+
1105
+ def trace_model_method
1106
+ select_model_and_method("Trace") do |model, method_info|
1107
+ run_trace_for_method(model, method_info)
1108
+ end
1109
+ end
1110
+
1111
+ def trace_service_method
1112
+ select_service_and_method("Trace") do |service, method_info|
1113
+ run_trace_for_method(service, method_info)
1114
+ end
1115
+ end
1116
+
1117
+ def trace_other_method
1118
+ select_other_and_method("Trace") do |file, method_info|
1119
+ run_trace_for_method(file, method_info)
1120
+ end
1121
+ end
1122
+
1123
+ def run_trace_for_method(file_info, method_info)
1124
+ result_path = nil
1125
+
1126
+ @go_ui.show_spinner("Tracing #{method_info[:name]}...") do
1127
+ begin
1128
+ project_root = Dir.pwd
1129
+ path = File.expand_path(file_info[:path])
1130
+
1131
+ trace_json = Tng::Analyzer::Context.analyze_symbolic_trace(
1132
+ project_root,
1133
+ path,
1134
+ method_info[:name],
1135
+ nil
1136
+ )
1137
+
1138
+ f = Tempfile.new(['trace', '.json'])
1139
+ f.write(JSON.generate(trace_json))
1140
+ f.close
1141
+ result_path = f.path
1142
+
1143
+ { success: true, message: "Trace generated" }
1144
+ rescue => e
1145
+ { success: false, message: e.message }
1146
+ end
1147
+ end
1148
+
1149
+ if result_path
1150
+ @go_ui.show_trace_results(result_path)
1151
+ File.unlink(result_path) if File.exist?(result_path)
1152
+ end
1153
+ end
1154
+
1155
+ def run_direct_trace
1156
+ file_path = params[:file]
1157
+ method_name = params[:method]
1158
+
1159
+ unless method_name
1160
+ puts @pastel.decorate("Please specify a method name with --method or -m", Tng::UI::Theme.color(:error))
1161
+ return
1162
+ end
1163
+
1164
+ full_path = File.expand_path(file_path)
1165
+ unless File.exist?(full_path)
1166
+ puts @pastel.decorate("File not found: #{full_path}", Tng::UI::Theme.color(:error))
1167
+ return
1168
+ end
1169
+
1170
+ file_info = { path: full_path, name: File.basename(full_path) }
1171
+ method_info = { name: method_name }
1172
+
1173
+ run_trace_for_method(file_info, method_info)
1174
+ end
1175
+
1176
+ def select_controller_and_method(action_name)
1177
+ controllers = nil
1178
+ @go_ui.show_spinner("Analyzing controllers...") do
1179
+ controllers = Tng::Analyzers::Controller.files_in_dir("app/controllers").map do |file|
1180
+ relative_path = file[:path].gsub(%r{^.*app/controllers/}, "").gsub(".rb", "")
1181
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
1182
+ { name: namespaced_name, path: file[:path] }
1183
+ end
1184
+ { success: true, message: "Found #{controllers.length} controllers" }
1185
+ end
1186
+
1187
+ if controllers.empty?
1188
+ @go_ui.show_no_items("controllers")
1189
+ return
1190
+ end
1191
+
1192
+ items = controllers.map { |c| { name: c[:name], path: c[:path] } }
1193
+
1194
+ loop do
1195
+ selected = @go_ui.show_list_view("Select Controller to #{action_name}", items)
1196
+ return if selected == "back"
1197
+
1198
+ choice = controllers.find { |c| c[:name] == selected }
1199
+ next unless choice
1200
+
1201
+ methods = extract_controller_methods(choice)
1202
+ if methods.empty?
1203
+ @go_ui.show_no_items("methods in #{choice[:name]}")
1204
+ next
1205
+ end
1206
+
1207
+ m_items = methods.map { |m| { name: m[:name], path: choice[:name] } }
1208
+ m_selected = @go_ui.show_list_view("Select Method to #{action_name}", m_items)
1209
+ next if m_selected == "back"
1210
+
1211
+ m_choice = methods.find { |m| m[:name] == m_selected }
1212
+ next unless m_choice
1213
+
1214
+ yield(choice, m_choice)
1215
+ end
1216
+ end
1217
+
1218
+ def select_model_and_method(action_name)
1219
+ models = nil
1220
+ @go_ui.show_spinner("Analyzing models...") do
1221
+ models = Tng::Analyzers::Model.files_in_dir("app/models").map do |file|
1222
+ relative_path = file[:path].gsub(%r{^.*app/models/}, "").gsub(".rb", "")
1223
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
1224
+ { name: namespaced_name, path: file[:path] }
1225
+ end
1226
+ { success: true, message: "Found #{models.length} models" }
1227
+ end
1228
+
1229
+ return @go_ui.show_no_items("models") if models.empty?
1230
+
1231
+ items = models.map { |m| { name: m[:name], path: m[:path] } }
1232
+
1233
+ loop do
1234
+ selected = @go_ui.show_list_view("Select Model to #{action_name}", items)
1235
+ return if selected == "back"
1236
+
1237
+ choice = models.find { |m| m[:name] == selected }
1238
+ next unless choice
1239
+
1240
+ methods = extract_model_methods(choice)
1241
+ if methods.empty?
1242
+ @go_ui.show_no_items("methods in #{choice[:name]}")
1243
+ next
1244
+ end
1245
+
1246
+ m_items = methods.map { |m| { name: m[:name], path: choice[:name] } }
1247
+ m_selected = @go_ui.show_list_view("Select Method to #{action_name}", m_items)
1248
+ next if m_selected == "back"
1249
+
1250
+ m_choice = methods.find { |m| m[:name] == m_selected }
1251
+ yield(choice, m_choice)
1252
+ end
1253
+ end
1254
+
1255
+ def select_service_and_method(action_name)
1256
+ services = nil
1257
+ @go_ui.show_spinner("Analyzing services...") do
1258
+ services = Tng::Analyzers::Service.files_in_dir.map do |file|
1259
+ relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
1260
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
1261
+ { name: namespaced_name, path: file[:path] }
1262
+ end
1263
+ { success: true, message: "Found #{services.length} services" }
1264
+ end
1265
+
1266
+ return @go_ui.show_no_items("services") if services.empty?
1267
+
1268
+ items = services.map { |s| { name: s[:name], path: s[:path] } }
1269
+
1270
+ loop do
1271
+ selected = @go_ui.show_list_view("Select Service to #{action_name}", items)
1272
+ return if selected == "back"
1273
+
1274
+ choice = services.find { |s| s[:name] == selected }
1275
+ next unless choice
1276
+
1277
+ methods = extract_service_methods(choice)
1278
+ if methods.empty?
1279
+ @go_ui.show_no_items("methods in #{choice[:name]}")
1280
+ next
1281
+ end
1282
+
1283
+ m_items = methods.map { |m| { name: m[:name], path: choice[:name] } }
1284
+ m_selected = @go_ui.show_list_view("Select Method to #{action_name}", m_items)
1285
+ next if m_selected == "back"
1286
+
1287
+ m_choice = methods.find { |m| m[:name] == m_selected }
1288
+ yield(choice, m_choice)
1289
+ end
1290
+ end
1291
+
1292
+ def select_other_and_method(action_name)
1293
+ files = nil
1294
+ @go_ui.show_spinner("Analyzing other files...") do
1295
+ files = Tng::Analyzers::Other.files_in_dir.map do |file|
1296
+ relative_path = file[:relative_path].gsub(".rb", "")
1297
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
1298
+ { name: namespaced_name, path: file[:path], type: file[:type] }
1299
+ end
1300
+ { success: true, message: "Found #{files.length} other files" }
1301
+ end
1302
+
1303
+ return @go_ui.show_no_items("other files") if files.empty?
1304
+
1305
+ items = files.map { |f| { name: f[:name], path: f[:path] } }
1306
+
1307
+ loop do
1308
+ selected = @go_ui.show_list_view("Select File to #{action_name}", items)
1309
+ return if selected == "back"
1310
+
1311
+ choice = files.find { |f| f[:name] == selected }
1312
+ next unless choice
1313
+
1314
+ methods = extract_other_methods(choice)
1315
+ if methods.empty?
1316
+ @go_ui.show_no_items("methods in #{choice[:name]}")
1317
+ next
1318
+ end
1319
+
1320
+ m_items = methods.map { |m| { name: m[:name], path: choice[:name] } }
1321
+ m_selected = @go_ui.show_list_view("Select Method to #{action_name}", m_items)
1322
+ next if m_selected == "back"
1323
+
1324
+ m_choice = methods.find { |m| m[:name] == m_selected }
1325
+ yield(choice, m_choice)
1326
+ end
1327
+ end
1072
1328
  end
1073
1329
 
1330
+
1331
+
1074
1332
  cli = CLI.new
1075
1333
  cli.start
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
data/binaries/tng.bundle CHANGED
Binary file
@@ -17,14 +17,10 @@ module Tng
17
17
  @go_ui = go_ui
18
18
  end
19
19
 
20
- # ... (rest of methods)
21
-
22
20
  def display_audit_results(result)
23
21
  audit_results = result[:audit_results]
24
22
  return unless audit_results
25
23
 
26
- # Use Go UI to display rich audit results
27
- # "issues" type handles both issues and behaviours in the unified view
28
24
  @go_ui.show_audit_results(audit_results, "issues")
29
25
  end
30
26
 
@@ -52,8 +48,6 @@ module Tng
52
48
  generate_test_for_file(resolved_path, method_name, type)
53
49
  end
54
50
 
55
- private
56
-
57
51
  def suggest_similar_files(file_path)
58
52
  base_name = File.basename(file_path, ".rb")
59
53
 
@@ -106,11 +100,14 @@ module Tng
106
100
 
107
101
  if result&.dig(:error)
108
102
  @go_ui.display_error(@pastel.red("❌ #{@params[:audit] ? "Audit" : "Test generation"} failed: #{result[:message]}"))
103
+ elsif result&.dig(:trace_generated)
104
+ if result[:file_path]
105
+ @go_ui.show_trace_results(result[:file_path])
106
+ File.unlink(result[:file_path]) if File.exist?(result[:file_path])
107
+ end
109
108
  elsif @params[:audit]
110
- # Audit mode - display results inline
111
109
  display_audit_results(result)
112
110
  elsif result && result[:file_path]
113
- # Test generation mode - show post-generation menu
114
111
  @show_post_generation_menu.call(result)
115
112
  else
116
113
  @go_ui.display_error(@pastel.red("❌ Failed to generate test"))
@@ -129,8 +126,9 @@ module Tng
129
126
  def generate_test_result(file_object, method_info, type, progress)
130
127
  generator = Tng::Services::TestGenerator.new(@http_client)
131
128
 
132
- if @params[:audit]
133
- # Audit mode - return issues and behaviours
129
+ if @params[:trace]
130
+ perform_trace_analysis(file_object, method_info, progress)
131
+ elsif @params[:audit]
134
132
  case type
135
133
  when "controller" then generator.run_audit_for_controller_method(file_object, method_info, progress: progress)
136
134
  when "model" then generator.run_audit_for_model_method(file_object, method_info, progress: progress)
@@ -138,7 +136,6 @@ module Tng
138
136
  else generator.run_audit_for_other_method(file_object, method_info, progress: progress)
139
137
  end
140
138
  else
141
- # Test generation mode
142
139
  case type
143
140
  when "controller" then generator.run_for_controller_method(file_object, method_info, progress: progress)
144
141
  when "model" then generator.run_for_model_method(file_object, method_info, progress: progress)
@@ -147,6 +144,33 @@ module Tng
147
144
  end
148
145
  end
149
146
  end
147
+
148
+ def perform_trace_analysis(file_object, method_info, progress)
149
+ progress.update("Tracing method execution...", 50)
150
+
151
+ project_root = Dir.pwd
152
+ path = File.expand_path(file_object[:path])
153
+
154
+ begin
155
+ trace_json = Tng::Analyzer::Context.analyze_symbolic_trace(
156
+ project_root,
157
+ path,
158
+ method_info[:name],
159
+ nil
160
+ )
161
+
162
+ f = Tempfile.new(['trace', '.json'])
163
+ f.write(JSON.generate(trace_json))
164
+ f.close
165
+ result_path = f.path
166
+
167
+ progress.update("Trace generated!", 100)
168
+
169
+ { trace_generated: true, file_path: result_path }
170
+ rescue => e
171
+ { error: true, message: e.message }
172
+ end
173
+ end
150
174
  end
151
175
  end
152
176
  end
@@ -253,6 +253,14 @@ module Tng
253
253
  File.unlink(input_path) if input_path && File.exist?(input_path)
254
254
  end
255
255
 
256
+ # Show symbolic trace results
257
+ # @param trace_file_path [String] Path to JSON trace file
258
+ def show_trace_results(trace_file_path)
259
+ system(@binary_path, "trace-results", "--file", trace_file_path)
260
+ rescue StandardError => e
261
+ puts "Trace results error: #{e.message}"
262
+ end
263
+
256
264
  # Show authentication error
257
265
  # @param message [String] Error message to display
258
266
  def show_auth_error(message = "Authentication failed")
@@ -320,11 +328,65 @@ module Tng
320
328
  # Show help
321
329
  # @param version [String] Version to display
322
330
  def show_help(version)
323
- system(@binary_path, "help-box", "--version", version)
331
+ help_data = {
332
+ title: "TNG - LLM-Powered Rails Test Generator",
333
+ usages: [
334
+ { cmd: "bundle exec tng", desc: "Interactive mode" },
335
+ { cmd: "bundle exec tng --file=FILE --method=METHOD", desc: "Direct test generation" },
336
+ { cmd: "bundle exec tng --file=FILE --method=METHOD --audit", desc: "Direct audit mode" },
337
+ { cmd: "bundle exec tng --file=FILE --method=METHOD --trace", desc: "Symbolic trace mode" }
338
+ ],
339
+ features: [
340
+ "Controllers, Models, Services",
341
+ "17+ other file types (Jobs, Helpers, Lib, Policies, etc.)",
342
+ "Per-method test generation",
343
+ "Code audit for issues & behaviors",
344
+ "Symbolic execution traces",
345
+ "Searchable method lists"
346
+ ],
347
+ options: [
348
+ { flag: "-h, --help", desc: "Show this help message" },
349
+ { flag: "--file=PATH", desc: "Target file path (enables direct CLI mode)" },
350
+ { flag: "--method=NAME", desc: "Method name to generate test for" },
351
+ { flag: "--audit", desc: "Run audit mode instead of test generation" },
352
+ { flag: "--trace", desc: "Run symbolic trace mode" }
353
+ ],
354
+ how_to: [
355
+ "Run 'bundle exec tng' to start the interactive interface",
356
+ "Select Controller, Model, Service, or Other to test",
357
+ "Choose a specific method from the list",
358
+ "Use search/filter to find methods quickly"
359
+ ],
360
+ footer: "Happy testing! "
361
+ }
362
+
363
+ system(@binary_path, "help-box", "--version", version, "--data", JSON.generate(help_data))
324
364
  rescue StandardError => e
325
365
  puts "Help error: #{e.message}"
326
366
  end
327
367
 
368
+ # Display error message using standard output (fallback for simple messages)
369
+ def display_error(message)
370
+ system(@binary_path, "auth-error", "--message", message, "--title", "Error")
371
+ end
372
+
373
+ # Display warning message
374
+ def display_warning(message)
375
+ # Re-using auth-error for now but with different color/prefix if possible, or just puts if binary doesn't support generic warning
376
+ puts message
377
+ end
378
+
379
+ # Display info message
380
+ def display_info(message)
381
+ puts message
382
+ end
383
+
384
+ # Display list of items
385
+ def display_list(title, items)
386
+ puts title
387
+ items.each { |item| puts " - #{item}" }
388
+ end
389
+
328
390
  # Show system status
329
391
  # @param status [Hash] Status data from testng
330
392
  def show_system_status(status)
data/lib/tng/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tng
4
- VERSION = "0.4.4"
4
+ VERSION = "0.4.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tng
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - ralucab
@@ -222,7 +222,7 @@ post_install_message: "┌ TNG ────────────────
222
222
  \ │\n│ • bundle exec
223
223
  tng --help - Show help information │\n│ │\n│
224
224
  \ \U0001F4A1 Generate tests for individual methods with precision │\n└────────────────────────────────────────────────────────────
225
- v0.4.4 ┘\n"
225
+ v0.4.5 ┘\n"
226
226
  rdoc_options: []
227
227
  require_paths:
228
228
  - lib