universal_document_processor 1.0.3 → 1.0.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 +4 -4
- data/ISSUES_ANALYSIS.md +295 -0
- data/PERFORMANCE.md +492 -0
- data/USER_GUIDE.md +597 -0
- data/debug_test.rb +35 -0
- data/lib/universal_document_processor/document.rb +5 -1
- data/lib/universal_document_processor/processors/base_processor.rb +5 -1
- data/lib/universal_document_processor/processors/pdf_processor.rb +17 -0
- data/lib/universal_document_processor/version.rb +1 -1
- data/test_ai_dependency.rb +80 -0
- data/test_core_functionality.rb +280 -0
- data/test_performance_memory.rb +271 -0
- data/test_published_gem.rb +349 -0
- metadata +20 -6
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Add lib directory to load path
|
4
|
+
$LOAD_PATH.unshift File.expand_path('lib', __dir__)
|
5
|
+
|
6
|
+
# Load the gem
|
7
|
+
require 'universal_document_processor'
|
8
|
+
|
9
|
+
puts "Testing AI Dependency Handling"
|
10
|
+
puts "=" * 50
|
11
|
+
|
12
|
+
# Test 1: Check AI availability without API key
|
13
|
+
puts "\n1. Testing AI availability without API key:"
|
14
|
+
ai_available = UniversalDocumentProcessor.ai_available?
|
15
|
+
puts " AI Available: #{ai_available}"
|
16
|
+
|
17
|
+
# Test 2: Create AI agent without API key
|
18
|
+
puts "\n2. Creating AI agent without API key:"
|
19
|
+
agent = UniversalDocumentProcessor.create_ai_agent
|
20
|
+
puts " Agent created: #{agent.class}"
|
21
|
+
puts " AI enabled: #{agent.ai_enabled}"
|
22
|
+
puts " AI available: #{agent.ai_available?}"
|
23
|
+
|
24
|
+
# Test 3: Try to use AI methods without API key
|
25
|
+
puts "\n3. Testing AI methods without API key:"
|
26
|
+
|
27
|
+
# Create a sample text file
|
28
|
+
require 'tempfile'
|
29
|
+
sample_file = Tempfile.new(['test', '.txt'])
|
30
|
+
sample_file.write("This is a test document for AI processing.")
|
31
|
+
sample_file.close
|
32
|
+
|
33
|
+
begin
|
34
|
+
result = UniversalDocumentProcessor.ai_analyze(sample_file.path)
|
35
|
+
puts " ERROR: Should have raised an exception!"
|
36
|
+
rescue UniversalDocumentProcessor::DependencyMissingError => e
|
37
|
+
puts " ✓ Correctly raised DependencyMissingError: #{e.message}"
|
38
|
+
rescue => e
|
39
|
+
puts " ✗ Unexpected error: #{e.class} - #{e.message}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Test 4: Check available features
|
43
|
+
puts "\n4. Available features:"
|
44
|
+
features = UniversalDocumentProcessor.available_features
|
45
|
+
puts " Features: #{features.join(', ')}"
|
46
|
+
puts " AI processing included: #{features.include?(:ai_processing)}"
|
47
|
+
|
48
|
+
# Test 5: Check optional dependencies
|
49
|
+
puts "\n5. Optional dependencies:"
|
50
|
+
optional_deps = UniversalDocumentProcessor.optional_dependencies
|
51
|
+
puts " Optional dependencies: #{optional_deps.keys.join(', ')}"
|
52
|
+
|
53
|
+
missing_deps = UniversalDocumentProcessor.missing_dependencies
|
54
|
+
puts " Missing dependencies: #{missing_deps.join(', ')}"
|
55
|
+
|
56
|
+
# Test 6: Installation instructions
|
57
|
+
puts "\n6. Installation instructions:"
|
58
|
+
instructions = UniversalDocumentProcessor.installation_instructions
|
59
|
+
puts instructions
|
60
|
+
|
61
|
+
# Test 7: Test with API key if provided
|
62
|
+
if ENV['OPENAI_API_KEY'] && !ENV['OPENAI_API_KEY'].empty?
|
63
|
+
puts "\n7. Testing with API key:"
|
64
|
+
ai_available_with_key = UniversalDocumentProcessor.ai_available?
|
65
|
+
puts " AI Available with key: #{ai_available_with_key}"
|
66
|
+
|
67
|
+
agent_with_key = UniversalDocumentProcessor.create_ai_agent
|
68
|
+
puts " Agent AI enabled: #{agent_with_key.ai_enabled}"
|
69
|
+
else
|
70
|
+
puts "\n7. Skipping API key test (OPENAI_API_KEY not set)"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Clean up
|
74
|
+
sample_file.unlink
|
75
|
+
|
76
|
+
puts "\n" + "=" * 50
|
77
|
+
puts "AI Dependency Test Complete!"
|
78
|
+
puts "✓ AI features are properly optional"
|
79
|
+
puts "✓ Clear error messages when dependencies missing"
|
80
|
+
puts "✓ Graceful degradation when features unavailable"
|
@@ -0,0 +1,280 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Add lib directory to load path
|
4
|
+
$LOAD_PATH.unshift File.expand_path('lib', __dir__)
|
5
|
+
|
6
|
+
# Load the gem
|
7
|
+
require 'universal_document_processor'
|
8
|
+
require 'tempfile'
|
9
|
+
|
10
|
+
puts "Testing Core Functionality"
|
11
|
+
puts "=" * 50
|
12
|
+
|
13
|
+
test_count = 0
|
14
|
+
passed_count = 0
|
15
|
+
|
16
|
+
def test(description)
|
17
|
+
global_test_count = caller_locations.first.lineno
|
18
|
+
print "#{global_test_count}. #{description}... "
|
19
|
+
|
20
|
+
begin
|
21
|
+
yield
|
22
|
+
puts "✓ PASS"
|
23
|
+
return true
|
24
|
+
rescue => e
|
25
|
+
puts "✗ FAIL: #{e.message}"
|
26
|
+
puts " #{e.backtrace.first}" if ENV['DEBUG']
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create sample files for testing
|
32
|
+
puts "\nCreating sample files..."
|
33
|
+
|
34
|
+
# Text file
|
35
|
+
txt_file = Tempfile.new(['test', '.txt'])
|
36
|
+
txt_file.write("This is a sample text file.\nIt has multiple lines.\nUsed for testing.")
|
37
|
+
txt_file.close
|
38
|
+
|
39
|
+
# CSV file
|
40
|
+
csv_file = Tempfile.new(['test', '.csv'])
|
41
|
+
csv_file.write("Name,Age,City\nJohn,25,New York\nJane,30,Los Angeles\nBob,35,Chicago")
|
42
|
+
csv_file.close
|
43
|
+
|
44
|
+
# TSV file
|
45
|
+
tsv_file = Tempfile.new(['test', '.tsv'])
|
46
|
+
tsv_file.write("Name\tAge\tCity\nJohn\t25\tNew York\nJane\t30\tLos Angeles\nBob\t35\tChicago")
|
47
|
+
tsv_file.close
|
48
|
+
|
49
|
+
# JSON file
|
50
|
+
json_file = Tempfile.new(['test', '.json'])
|
51
|
+
json_file.write('{"name": "Test Document", "type": "sample", "data": [1, 2, 3, 4, 5]}')
|
52
|
+
json_file.close
|
53
|
+
|
54
|
+
# XML file
|
55
|
+
xml_file = Tempfile.new(['test', '.xml'])
|
56
|
+
xml_file.write(<<~XML)
|
57
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
58
|
+
<document>
|
59
|
+
<title>Sample XML Document</title>
|
60
|
+
<content>This is a sample XML file for testing.</content>
|
61
|
+
</document>
|
62
|
+
XML
|
63
|
+
xml_file.close
|
64
|
+
|
65
|
+
puts "Sample files created successfully!"
|
66
|
+
|
67
|
+
# Run tests
|
68
|
+
puts "\nRunning Core Tests:"
|
69
|
+
puts "-" * 30
|
70
|
+
|
71
|
+
# Test 1: Version number
|
72
|
+
test_count += 1
|
73
|
+
passed = test("Version number is defined") do
|
74
|
+
version = UniversalDocumentProcessor::VERSION
|
75
|
+
raise "Version is nil" if version.nil?
|
76
|
+
raise "Version format invalid" unless version.match?(/\d+\.\d+\.\d+/)
|
77
|
+
end
|
78
|
+
passed_count += 1 if passed
|
79
|
+
|
80
|
+
# Test 2: Text file processing
|
81
|
+
test_count += 1
|
82
|
+
passed = test("Text file processing") do
|
83
|
+
result = UniversalDocumentProcessor.process(txt_file.path)
|
84
|
+
raise "Result is not a hash" unless result.is_a?(Hash)
|
85
|
+
raise "Missing text key" unless result.has_key?(:text)
|
86
|
+
raise "Missing metadata key" unless result.has_key?(:metadata)
|
87
|
+
raise "Text content incorrect" unless result[:text].include?("sample text file")
|
88
|
+
raise "Format incorrect" unless result[:metadata][:format] == "txt"
|
89
|
+
end
|
90
|
+
passed_count += 1 if passed
|
91
|
+
|
92
|
+
# Test 3: Text extraction
|
93
|
+
test_count += 1
|
94
|
+
passed = test("Text extraction method") do
|
95
|
+
text = UniversalDocumentProcessor.extract_text(txt_file.path)
|
96
|
+
raise "Text is not a string" unless text.is_a?(String)
|
97
|
+
raise "Text content missing" unless text.include?("sample text file")
|
98
|
+
end
|
99
|
+
passed_count += 1 if passed
|
100
|
+
|
101
|
+
# Test 4: Metadata extraction
|
102
|
+
test_count += 1
|
103
|
+
passed = test("Metadata extraction") do
|
104
|
+
metadata = UniversalDocumentProcessor.get_metadata(txt_file.path)
|
105
|
+
raise "Metadata is not a hash" unless metadata.is_a?(Hash)
|
106
|
+
raise "Format missing" unless metadata[:format] == "txt"
|
107
|
+
raise "File size missing" unless metadata[:file_size] > 0
|
108
|
+
end
|
109
|
+
passed_count += 1 if passed
|
110
|
+
|
111
|
+
# Test 5: CSV processing
|
112
|
+
test_count += 1
|
113
|
+
passed = test("CSV file processing") do
|
114
|
+
result = UniversalDocumentProcessor.process(csv_file.path)
|
115
|
+
raise "Result is not a hash" unless result.is_a?(Hash)
|
116
|
+
raise "Missing tables key" unless result.has_key?(:tables)
|
117
|
+
raise "Format incorrect" unless result[:metadata][:format] == "csv"
|
118
|
+
raise "Delimiter incorrect" unless result[:metadata][:delimiter] == "comma"
|
119
|
+
raise "No tables found" unless result[:tables].length > 0
|
120
|
+
end
|
121
|
+
passed_count += 1 if passed
|
122
|
+
|
123
|
+
# Test 6: TSV processing
|
124
|
+
test_count += 1
|
125
|
+
passed = test("TSV file processing") do
|
126
|
+
result = UniversalDocumentProcessor.process(tsv_file.path)
|
127
|
+
raise "Result is not a hash" unless result.is_a?(Hash)
|
128
|
+
raise "Missing tables key" unless result.has_key?(:tables)
|
129
|
+
raise "Format incorrect" unless result[:metadata][:format] == "tsv"
|
130
|
+
raise "Delimiter incorrect" unless result[:metadata][:delimiter] == "tab"
|
131
|
+
raise "No tables found" unless result[:tables].length > 0
|
132
|
+
end
|
133
|
+
passed_count += 1 if passed
|
134
|
+
|
135
|
+
# Test 7: JSON processing
|
136
|
+
test_count += 1
|
137
|
+
passed = test("JSON file processing") do
|
138
|
+
result = UniversalDocumentProcessor.process(json_file.path)
|
139
|
+
raise "Result is not a hash" unless result.is_a?(Hash)
|
140
|
+
raise "Format incorrect" unless result[:metadata][:format] == "json"
|
141
|
+
raise "Text missing" unless result[:text].include?("Test Document")
|
142
|
+
end
|
143
|
+
passed_count += 1 if passed
|
144
|
+
|
145
|
+
# Test 8: XML processing
|
146
|
+
test_count += 1
|
147
|
+
passed = test("XML file processing") do
|
148
|
+
result = UniversalDocumentProcessor.process(xml_file.path)
|
149
|
+
raise "Result is not a hash" unless result.is_a?(Hash)
|
150
|
+
raise "Format incorrect" unless result[:metadata][:format] == "xml"
|
151
|
+
raise "Text missing" unless result[:text].include?("Sample XML Document")
|
152
|
+
end
|
153
|
+
passed_count += 1 if passed
|
154
|
+
|
155
|
+
# Test 9: Batch processing
|
156
|
+
test_count += 1
|
157
|
+
passed = test("Batch processing") do
|
158
|
+
files = [txt_file.path, csv_file.path, json_file.path]
|
159
|
+
results = UniversalDocumentProcessor.batch_process(files)
|
160
|
+
raise "Results not array" unless results.is_a?(Array)
|
161
|
+
raise "Wrong number of results" unless results.length == 3
|
162
|
+
results.each do |result|
|
163
|
+
raise "Missing text or error key" unless result.has_key?(:text) || result.has_key?(:error)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
passed_count += 1 if passed
|
167
|
+
|
168
|
+
# Test 10: Available features
|
169
|
+
test_count += 1
|
170
|
+
passed = test("Available features check") do
|
171
|
+
features = UniversalDocumentProcessor.available_features
|
172
|
+
raise "Features not array" unless features.is_a?(Array)
|
173
|
+
raise "Missing text processing" unless features.include?(:text_processing)
|
174
|
+
raise "Missing CSV processing" unless features.include?(:csv_processing)
|
175
|
+
raise "Missing TSV processing" unless features.include?(:tsv_processing)
|
176
|
+
end
|
177
|
+
passed_count += 1 if passed
|
178
|
+
|
179
|
+
# Test 11: Dependency checking
|
180
|
+
test_count += 1
|
181
|
+
passed = test("Dependency availability check") do
|
182
|
+
# These may or may not be available, just test the method works
|
183
|
+
pdf_available = UniversalDocumentProcessor.dependency_available?(:pdf_reader)
|
184
|
+
raise "Dependency check failed" unless [true, false].include?(pdf_available)
|
185
|
+
end
|
186
|
+
passed_count += 1 if passed
|
187
|
+
|
188
|
+
# Test 12: Text quality analysis
|
189
|
+
test_count += 1
|
190
|
+
passed = test("Text quality analysis") do
|
191
|
+
analysis = UniversalDocumentProcessor.analyze_text_quality("Clean text")
|
192
|
+
raise "Analysis not hash" unless analysis.is_a?(Hash)
|
193
|
+
raise "Missing valid_characters" unless analysis.has_key?(:valid_characters)
|
194
|
+
raise "Missing invalid_characters" unless analysis.has_key?(:invalid_characters)
|
195
|
+
end
|
196
|
+
passed_count += 1 if passed
|
197
|
+
|
198
|
+
# Test 13: Text cleaning
|
199
|
+
test_count += 1
|
200
|
+
passed = test("Text cleaning") do
|
201
|
+
dirty_text = "Clean\x00text"
|
202
|
+
clean_text = UniversalDocumentProcessor.clean_text(dirty_text)
|
203
|
+
raise "Cleaning failed" if clean_text.include?("\x00")
|
204
|
+
end
|
205
|
+
passed_count += 1 if passed
|
206
|
+
|
207
|
+
# Test 14: Japanese text detection
|
208
|
+
test_count += 1
|
209
|
+
passed = test("Japanese text detection") do
|
210
|
+
english = "This is English"
|
211
|
+
japanese = "これは日本語"
|
212
|
+
raise "English detected as Japanese" if UniversalDocumentProcessor.japanese_text?(english)
|
213
|
+
raise "Japanese not detected" unless UniversalDocumentProcessor.japanese_text?(japanese)
|
214
|
+
end
|
215
|
+
passed_count += 1 if passed
|
216
|
+
|
217
|
+
# Test 15: Optional dependencies info
|
218
|
+
test_count += 1
|
219
|
+
passed = test("Optional dependencies information") do
|
220
|
+
optional_deps = UniversalDocumentProcessor.optional_dependencies
|
221
|
+
raise "Optional deps not hash" unless optional_deps.is_a?(Hash)
|
222
|
+
raise "Missing pdf-reader" unless optional_deps.has_key?('pdf-reader')
|
223
|
+
|
224
|
+
missing_deps = UniversalDocumentProcessor.missing_dependencies
|
225
|
+
raise "Missing deps not array" unless missing_deps.is_a?(Array)
|
226
|
+
|
227
|
+
instructions = UniversalDocumentProcessor.installation_instructions
|
228
|
+
raise "Instructions not string" unless instructions.is_a?(String)
|
229
|
+
end
|
230
|
+
passed_count += 1 if passed
|
231
|
+
|
232
|
+
# Test 16: AI availability check (should be false without API key)
|
233
|
+
test_count += 1
|
234
|
+
passed = test("AI availability check") do
|
235
|
+
ai_available = UniversalDocumentProcessor.ai_available?
|
236
|
+
raise "AI should not be available without key" if ai_available
|
237
|
+
end
|
238
|
+
passed_count += 1 if passed
|
239
|
+
|
240
|
+
# Test 17: Error handling for unsupported format
|
241
|
+
test_count += 1
|
242
|
+
passed = test("Error handling for unsupported format") do
|
243
|
+
unsupported_file = Tempfile.new(['test', '.unknown'])
|
244
|
+
unsupported_file.write("test content")
|
245
|
+
unsupported_file.close
|
246
|
+
|
247
|
+
begin
|
248
|
+
UniversalDocumentProcessor.process(unsupported_file.path)
|
249
|
+
raise "Should have raised UnsupportedFormatError"
|
250
|
+
rescue UniversalDocumentProcessor::UnsupportedFormatError
|
251
|
+
# Expected error
|
252
|
+
rescue => e
|
253
|
+
raise "Wrong error type: #{e.class}"
|
254
|
+
ensure
|
255
|
+
unsupported_file.unlink
|
256
|
+
end
|
257
|
+
end
|
258
|
+
passed_count += 1 if passed
|
259
|
+
|
260
|
+
# Clean up
|
261
|
+
puts "\nCleaning up temporary files..."
|
262
|
+
[txt_file, csv_file, tsv_file, json_file, xml_file].each do |file|
|
263
|
+
file.unlink if File.exist?(file.path)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Results
|
267
|
+
puts "\n" + "=" * 50
|
268
|
+
puts "Test Results:"
|
269
|
+
puts " Total tests: #{test_count}"
|
270
|
+
puts " Passed: #{passed_count}"
|
271
|
+
puts " Failed: #{test_count - passed_count}"
|
272
|
+
puts " Success rate: #{((passed_count.to_f / test_count) * 100).round(1)}%"
|
273
|
+
|
274
|
+
if passed_count == test_count
|
275
|
+
puts "\n🎉 All tests passed! Core functionality is working correctly."
|
276
|
+
exit 0
|
277
|
+
else
|
278
|
+
puts "\n❌ Some tests failed. Please check the issues above."
|
279
|
+
exit 1
|
280
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Performance and Memory Usage Analysis for Universal Document Processor
|
4
|
+
# This test checks if we need to add performance guidelines and memory usage documentation
|
5
|
+
|
6
|
+
puts "🚀 Performance & Memory Analysis - Universal Document Processor"
|
7
|
+
puts "=" * 70
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift File.expand_path('lib', __dir__)
|
10
|
+
require 'universal_document_processor'
|
11
|
+
require 'tempfile'
|
12
|
+
require 'benchmark'
|
13
|
+
|
14
|
+
# Helper to get memory usage (Windows-specific)
|
15
|
+
def get_memory_usage
|
16
|
+
begin
|
17
|
+
result = `tasklist /FI "PID eq #{Process.pid}" /FO CSV 2>nul`
|
18
|
+
if result && !result.empty?
|
19
|
+
lines = result.split("\n")
|
20
|
+
if lines.length > 1
|
21
|
+
memory_str = lines[1].split(",")[4].gsub('"', '').gsub(',', '')
|
22
|
+
return memory_str.to_i # KB
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue
|
26
|
+
# Fallback for non-Windows or error cases
|
27
|
+
end
|
28
|
+
return 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def format_memory(kb)
|
32
|
+
if kb > 1024
|
33
|
+
"#{(kb / 1024.0).round(1)} MB"
|
34
|
+
else
|
35
|
+
"#{kb} KB"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_test_file(size_description, content_generator)
|
40
|
+
file = Tempfile.new(['perf_test', '.txt'])
|
41
|
+
content = content_generator.call
|
42
|
+
file.write(content)
|
43
|
+
file.close
|
44
|
+
|
45
|
+
actual_size = File.size(file.path)
|
46
|
+
puts " 📁 Created #{size_description}: #{format_memory(actual_size / 1024)} (#{file.path})"
|
47
|
+
|
48
|
+
return file, actual_size
|
49
|
+
end
|
50
|
+
|
51
|
+
issues_found = []
|
52
|
+
performance_concerns = []
|
53
|
+
|
54
|
+
puts "\n📊 PERFORMANCE TESTING"
|
55
|
+
puts "-" * 50
|
56
|
+
|
57
|
+
# Test 1: Small file performance (baseline)
|
58
|
+
puts "\n1️⃣ Small File Performance (Baseline)"
|
59
|
+
small_file, small_size = create_test_file("small file", -> { "Hello World!\n" * 100 })
|
60
|
+
|
61
|
+
start_memory = get_memory_usage
|
62
|
+
time_taken = Benchmark.realtime do
|
63
|
+
result = UniversalDocumentProcessor.process(small_file.path)
|
64
|
+
end
|
65
|
+
end_memory = get_memory_usage
|
66
|
+
|
67
|
+
puts " ⏱️ Processing time: #{(time_taken * 1000).round(2)} ms"
|
68
|
+
puts " 🧠 Memory change: #{format_memory(end_memory - start_memory)}"
|
69
|
+
|
70
|
+
small_file.unlink
|
71
|
+
baseline_time = time_taken
|
72
|
+
|
73
|
+
# Test 2: Medium file performance
|
74
|
+
puts "\n2️⃣ Medium File Performance (1MB)"
|
75
|
+
medium_file, medium_size = create_test_file("medium file", -> { "This is a test line with some content.\n" * 25000 })
|
76
|
+
|
77
|
+
start_memory = get_memory_usage
|
78
|
+
time_taken = Benchmark.realtime do
|
79
|
+
result = UniversalDocumentProcessor.process(medium_file.path)
|
80
|
+
end
|
81
|
+
end_memory = get_memory_usage
|
82
|
+
|
83
|
+
puts " ⏱️ Processing time: #{(time_taken * 1000).round(2)} ms"
|
84
|
+
puts " 🧠 Memory change: #{format_memory(end_memory - start_memory)}"
|
85
|
+
puts " 📈 Speed ratio: #{(time_taken / baseline_time).round(1)}x slower than baseline"
|
86
|
+
|
87
|
+
if time_taken > 2.0
|
88
|
+
performance_concerns << "Medium files (1MB) take #{time_taken.round(2)} seconds to process"
|
89
|
+
end
|
90
|
+
|
91
|
+
medium_file.unlink
|
92
|
+
|
93
|
+
# Test 3: Large file performance
|
94
|
+
puts "\n3️⃣ Large File Performance (5MB)"
|
95
|
+
large_file, large_size = create_test_file("large file", -> { "This is a longer test line with more content to simulate real documents.\n" * 75000 })
|
96
|
+
|
97
|
+
start_memory = get_memory_usage
|
98
|
+
time_taken = Benchmark.realtime do
|
99
|
+
result = UniversalDocumentProcessor.process(large_file.path)
|
100
|
+
end
|
101
|
+
end_memory = get_memory_usage
|
102
|
+
|
103
|
+
puts " ⏱️ Processing time: #{(time_taken * 1000).round(2)} ms"
|
104
|
+
puts " 🧠 Memory change: #{format_memory(end_memory - start_memory)}"
|
105
|
+
puts " 📈 Speed ratio: #{(time_taken / baseline_time).round(1)}x slower than baseline"
|
106
|
+
|
107
|
+
if time_taken > 10.0
|
108
|
+
performance_concerns << "Large files (5MB) take #{time_taken.round(2)} seconds to process"
|
109
|
+
end
|
110
|
+
|
111
|
+
if (end_memory - start_memory) > 100000 # 100MB
|
112
|
+
performance_concerns << "Large files use #{format_memory(end_memory - start_memory)} of memory"
|
113
|
+
end
|
114
|
+
|
115
|
+
large_file.unlink
|
116
|
+
|
117
|
+
puts "\n💾 MEMORY USAGE TESTING"
|
118
|
+
puts "-" * 50
|
119
|
+
|
120
|
+
# Test 4: Memory usage with multiple files
|
121
|
+
puts "\n4️⃣ Batch Processing Memory Test"
|
122
|
+
files = []
|
123
|
+
file_sizes = []
|
124
|
+
|
125
|
+
5.times do |i|
|
126
|
+
file, size = create_test_file("batch file #{i+1}", -> { "Batch processing test content line #{i}.\n" * 5000 })
|
127
|
+
files << file.path
|
128
|
+
file_sizes << size
|
129
|
+
end
|
130
|
+
|
131
|
+
total_file_size = file_sizes.sum
|
132
|
+
puts " 📦 Total file size: #{format_memory(total_file_size / 1024)}"
|
133
|
+
|
134
|
+
start_memory = get_memory_usage
|
135
|
+
time_taken = Benchmark.realtime do
|
136
|
+
results = UniversalDocumentProcessor.batch_process(files)
|
137
|
+
end
|
138
|
+
end_memory = get_memory_usage
|
139
|
+
|
140
|
+
memory_used = end_memory - start_memory
|
141
|
+
puts " ⏱️ Batch processing time: #{(time_taken * 1000).round(2)} ms"
|
142
|
+
puts " 🧠 Memory used: #{format_memory(memory_used)}"
|
143
|
+
puts " 📊 Memory efficiency: #{(memory_used.to_f / (total_file_size / 1024)).round(2)}x file size"
|
144
|
+
|
145
|
+
if memory_used > (total_file_size / 1024) * 3 # More than 3x file size
|
146
|
+
performance_concerns << "Batch processing uses #{(memory_used.to_f / (total_file_size / 1024)).round(1)}x the file size in memory"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Cleanup
|
150
|
+
files.each { |f| File.delete(f) if File.exist?(f) }
|
151
|
+
|
152
|
+
# Test 5: CSV/TSV processing performance
|
153
|
+
puts "\n5️⃣ Structured Data Processing Performance"
|
154
|
+
|
155
|
+
# Large CSV test
|
156
|
+
csv_content = "Name,Age,Email,Department,Salary,Location,Phone\n"
|
157
|
+
csv_content += 10000.times.map { |i| "User#{i},#{20+i%50},user#{i}@example.com,Dept#{i%10},#{30000+i*10},City#{i%100},555-#{i.to_s.rjust(4, '0')}" }.join("\n")
|
158
|
+
|
159
|
+
csv_file = Tempfile.new(['large', '.csv'])
|
160
|
+
csv_file.write(csv_content)
|
161
|
+
csv_file.close
|
162
|
+
|
163
|
+
csv_size = File.size(csv_file.path)
|
164
|
+
puts " 📊 Large CSV size: #{format_memory(csv_size / 1024)}"
|
165
|
+
|
166
|
+
start_memory = get_memory_usage
|
167
|
+
time_taken = Benchmark.realtime do
|
168
|
+
result = UniversalDocumentProcessor.process(csv_file.path)
|
169
|
+
end
|
170
|
+
end_memory = get_memory_usage
|
171
|
+
|
172
|
+
puts " ⏱️ CSV processing time: #{(time_taken * 1000).round(2)} ms"
|
173
|
+
puts " 🧠 Memory change: #{format_memory(end_memory - start_memory)}"
|
174
|
+
|
175
|
+
if time_taken > 5.0
|
176
|
+
performance_concerns << "Large CSV files (#{format_memory(csv_size / 1024)}) take #{time_taken.round(2)} seconds"
|
177
|
+
end
|
178
|
+
|
179
|
+
csv_file.unlink
|
180
|
+
|
181
|
+
# Test 6: Unicode content performance
|
182
|
+
puts "\n6️⃣ Unicode Content Performance"
|
183
|
+
unicode_content = "これは日本語のテストです。🌟 This includes emoji and special characters: áéíóú, ñ, ç, ü\n" * 5000
|
184
|
+
|
185
|
+
unicode_file = Tempfile.new(['unicode', '.txt'])
|
186
|
+
unicode_file.write(unicode_content)
|
187
|
+
unicode_file.close
|
188
|
+
|
189
|
+
start_memory = get_memory_usage
|
190
|
+
time_taken = Benchmark.realtime do
|
191
|
+
result = UniversalDocumentProcessor.process(unicode_file.path)
|
192
|
+
end
|
193
|
+
end_memory = get_memory_usage
|
194
|
+
|
195
|
+
puts " ⏱️ Unicode processing time: #{(time_taken * 1000).round(2)} ms"
|
196
|
+
puts " 🧠 Memory change: #{format_memory(end_memory - start_memory)}"
|
197
|
+
|
198
|
+
unicode_file.unlink
|
199
|
+
|
200
|
+
puts "\n" + "=" * 70
|
201
|
+
puts "🎯 PERFORMANCE & MEMORY ANALYSIS RESULTS"
|
202
|
+
puts "=" * 70
|
203
|
+
|
204
|
+
puts "\n📈 PERFORMANCE CONCERNS FOUND:"
|
205
|
+
if performance_concerns.empty?
|
206
|
+
puts "✅ No significant performance issues detected!"
|
207
|
+
puts " The gem performs well within reasonable limits."
|
208
|
+
else
|
209
|
+
performance_concerns.each_with_index do |concern, i|
|
210
|
+
puts "⚠️ #{i + 1}. #{concern}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
puts "\n📚 DOCUMENTATION RECOMMENDATIONS:"
|
215
|
+
|
216
|
+
puts "\n4️⃣ Performance Guidelines Needed:"
|
217
|
+
guidelines_needed = []
|
218
|
+
|
219
|
+
if performance_concerns.any? { |c| c.include?("seconds") }
|
220
|
+
guidelines_needed << "Processing time expectations for different file sizes"
|
221
|
+
guidelines_needed << "Recommended file size limits for real-time processing"
|
222
|
+
end
|
223
|
+
|
224
|
+
if performance_concerns.any? { |c| c.include?("memory") }
|
225
|
+
guidelines_needed << "Memory usage patterns and optimization tips"
|
226
|
+
guidelines_needed << "Best practices for batch processing large files"
|
227
|
+
end
|
228
|
+
|
229
|
+
guidelines_needed << "Performance comparison between different file formats"
|
230
|
+
guidelines_needed << "Optimization tips for production environments"
|
231
|
+
|
232
|
+
if guidelines_needed.any?
|
233
|
+
puts "📋 Suggested documentation additions:"
|
234
|
+
guidelines_needed.each_with_index do |guideline, i|
|
235
|
+
puts " #{i + 1}. #{guideline}"
|
236
|
+
end
|
237
|
+
else
|
238
|
+
puts "✅ Current performance is good - minimal documentation needed"
|
239
|
+
end
|
240
|
+
|
241
|
+
puts "\n5️⃣ Memory Usage Documentation Needed:"
|
242
|
+
memory_docs_needed = []
|
243
|
+
|
244
|
+
memory_docs_needed << "Expected memory usage patterns (typically 2-3x file size)"
|
245
|
+
memory_docs_needed << "Memory-efficient processing tips for large files"
|
246
|
+
memory_docs_needed << "Batch processing memory considerations"
|
247
|
+
memory_docs_needed << "When to process files individually vs. in batches"
|
248
|
+
|
249
|
+
puts "📋 Suggested memory usage documentation:"
|
250
|
+
memory_docs_needed.each_with_index do |doc, i|
|
251
|
+
puts " #{i + 1}. #{doc}"
|
252
|
+
end
|
253
|
+
|
254
|
+
puts "\n💡 SPECIFIC RECOMMENDATIONS:"
|
255
|
+
puts "1. Add a PERFORMANCE.md file with benchmarks and guidelines"
|
256
|
+
puts "2. Include memory usage examples in README"
|
257
|
+
puts "3. Add performance tips to method documentation"
|
258
|
+
puts "4. Consider adding a performance_info method to the gem"
|
259
|
+
puts "5. Document recommended file size limits for different use cases"
|
260
|
+
|
261
|
+
puts "\n🎯 CONCLUSION:"
|
262
|
+
if performance_concerns.length > 2
|
263
|
+
puts "❌ Performance documentation is NEEDED - several concerns found"
|
264
|
+
exit 1
|
265
|
+
elsif performance_concerns.length > 0
|
266
|
+
puts "⚠️ Performance documentation would be HELPFUL - some concerns found"
|
267
|
+
exit 2
|
268
|
+
else
|
269
|
+
puts "✅ Performance is good, but documentation would still be valuable for users"
|
270
|
+
exit 0
|
271
|
+
end
|