ukiryu 0.1.7 → 0.2.1
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/lib/ukiryu/cache.rb +6 -0
- data/lib/ukiryu/cache_registry.rb +64 -0
- data/lib/ukiryu/cli_commands/base_command.rb +6 -5
- data/lib/ukiryu/cli_commands/config_command.rb +7 -10
- data/lib/ukiryu/cli_commands/register_command.rb +27 -18
- data/lib/ukiryu/cli_commands/validate_command.rb +2 -2
- data/lib/ukiryu/command_builder.rb +83 -50
- data/lib/ukiryu/config.rb +13 -2
- data/lib/ukiryu/debug.rb +20 -9
- data/lib/ukiryu/definition/loader.rb +3 -3
- data/lib/ukiryu/errors.rb +37 -37
- data/lib/ukiryu/executable_locator.rb +40 -16
- data/lib/ukiryu/extractors/base_extractor.rb +2 -1
- data/lib/ukiryu/extractors/help_parser.rb +3 -0
- data/lib/ukiryu/logger.rb +51 -0
- data/lib/ukiryu/models/implementation_index.rb +2 -1
- data/lib/ukiryu/models/implementation_version.rb +18 -1
- data/lib/ukiryu/models/interface.rb +2 -1
- data/lib/ukiryu/models/run_environment.rb +0 -2
- data/lib/ukiryu/models/semantic_version.rb +174 -0
- data/lib/ukiryu/models/stage_metrics.rb +0 -1
- data/lib/ukiryu/register.rb +473 -232
- data/lib/ukiryu/shell/powershell.rb +192 -99
- data/lib/ukiryu/shell/sh.rb +4 -1
- data/lib/ukiryu/shell.rb +60 -2
- data/lib/ukiryu/tool/command_resolution.rb +2 -1
- data/lib/ukiryu/tool/executable_discovery.rb +14 -15
- data/lib/ukiryu/tool/loader.rb +543 -0
- data/lib/ukiryu/tool/version_detection.rb +1 -3
- data/lib/ukiryu/tool.rb +79 -87
- data/lib/ukiryu/tool_index.rb +127 -62
- data/lib/ukiryu/tools/base.rb +4 -2
- data/lib/ukiryu/type.rb +26 -15
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu.rb +1 -1
- data/spec/fixtures/profiles/ghostscript_10.0.yaml +50 -0
- data/spec/fixtures/register/tools/ghostscript/default/10.0.yaml +6 -0
- data/spec/fixtures/register/tools/inkscape/INKSCAPE_MAN.md +444 -0
- data/spec/spec_helper.rb +10 -6
- data/spec/support/tool_helper.rb +2 -0
- data/spec/ukiryu/definition/loader_spec.rb +2 -2
- data/spec/ukiryu/executor_spec.rb +6 -3
- data/spec/ukiryu/models/execution_report_spec.rb +3 -2
- data/spec/ukiryu/models/semantic_version_spec.rb +284 -0
- data/spec/ukiryu/shell/powershell_integration_spec.rb +165 -0
- data/spec/ukiryu/shell/powershell_real_command_spec.rb +143 -0
- data/spec/ukiryu/shell/powershell_spec.rb +228 -60
- data/spec/ukiryu/tool/loader_spec.rb +148 -0
- data/spec/ukiryu/tool_index_spec.rb +110 -18
- data/spec/ukiryu/tools/ghostscript_spec.rb +242 -0
- data/spec/ukiryu/tools/imagemagick_spec.rb +2 -1
- data/spec/ukiryu/tools/inkscape_spec.rb +4 -2
- metadata +21 -4
- data/lib/ukiryu/register_auto_manager.rb +0 -342
data/spec/spec_helper.rb
CHANGED
|
@@ -8,7 +8,7 @@ require 'timeout'
|
|
|
8
8
|
require 'ukiryu'
|
|
9
9
|
|
|
10
10
|
# Require all support files
|
|
11
|
-
Dir[File.join(__dir__,
|
|
11
|
+
Dir[File.join(__dir__, 'support', '**', '*.rb')].sort.each { |f| require f }
|
|
12
12
|
|
|
13
13
|
RSpec.configure do |config|
|
|
14
14
|
# Enable flags like --only-failures and --next-failure
|
|
@@ -40,12 +40,12 @@ RSpec.configure do |config|
|
|
|
40
40
|
# Set up test register path if UKIRYU_REGISTER is not already set
|
|
41
41
|
unless ENV['UKIRYU_REGISTER']
|
|
42
42
|
test_register = File.expand_path('fixtures/register', __dir__)
|
|
43
|
-
if Dir.exist?(test_register)
|
|
44
|
-
ENV['UKIRYU_REGISTER'] = test_register
|
|
45
|
-
Ukiryu::Register.default_register_path = test_register
|
|
46
|
-
end
|
|
43
|
+
ENV['UKIRYU_REGISTER'] = test_register if Dir.exist?(test_register)
|
|
47
44
|
end
|
|
48
45
|
|
|
46
|
+
# Reset the default register to pick up the test register
|
|
47
|
+
Ukiryu::Register.reset_default
|
|
48
|
+
|
|
49
49
|
# Reset ToolIndex to pick up the new register path
|
|
50
50
|
Ukiryu::ToolIndex.reset
|
|
51
51
|
|
|
@@ -59,11 +59,15 @@ RSpec.configure do |config|
|
|
|
59
59
|
# Reset singleton state before each test to prevent pollution
|
|
60
60
|
config.before(:each) do
|
|
61
61
|
Ukiryu::Config.reset!
|
|
62
|
-
Ukiryu::Register.
|
|
62
|
+
Ukiryu::Register.reset_default
|
|
63
63
|
Ukiryu::ToolIndex.reset
|
|
64
64
|
Ukiryu::Tool.clear_cache
|
|
65
65
|
Ukiryu::Runtime.instance.reset!
|
|
66
66
|
Ukiryu::Tools::Generator.clear_cache
|
|
67
|
+
Ukiryu::Shell.reset
|
|
68
|
+
|
|
69
|
+
# Clean up test shell environment variable that may be left by other tests
|
|
70
|
+
ENV.delete('UKIRYU_TEST_SHELL')
|
|
67
71
|
|
|
68
72
|
# Remove generated tool classes from Tools namespace
|
|
69
73
|
Ukiryu::Tools.constants.each do |const|
|
data/spec/support/tool_helper.rb
CHANGED
|
@@ -174,8 +174,8 @@ RSpec.describe Ukiryu::Definition::Loader do
|
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
describe '.profile_cache' do
|
|
177
|
-
it 'returns a
|
|
178
|
-
expect(described_class.profile_cache).to be_a(
|
|
177
|
+
it 'returns a bounded cache' do
|
|
178
|
+
expect(described_class.profile_cache).to be_a(Ukiryu::Cache)
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
it 'returns same cache on multiple calls' do
|
|
@@ -152,7 +152,8 @@ RSpec.describe Ukiryu::Executor do
|
|
|
152
152
|
# Use platform-appropriate command that exits with specific code
|
|
153
153
|
result = if Ukiryu::Platform.windows?
|
|
154
154
|
# Windows: PowerShell supports exit codes
|
|
155
|
-
executor.execute('powershell', ['-Command', 'exit 42'], allow_failure: true, shell: :powershell,
|
|
155
|
+
executor.execute('powershell', ['-Command', 'exit 42'], allow_failure: true, shell: :powershell,
|
|
156
|
+
timeout: 30)
|
|
156
157
|
else
|
|
157
158
|
executor.execute('sh', ['-c', 'exit 42'], allow_failure: true, shell: shell_symbol, timeout: 30)
|
|
158
159
|
end
|
|
@@ -215,7 +216,8 @@ RSpec.describe Ukiryu::Executor do
|
|
|
215
216
|
# Use echo as a cross-platform command that always works
|
|
216
217
|
result = if Ukiryu::Platform.windows?
|
|
217
218
|
# On Windows with PowerShell, use echo command via PowerShell
|
|
218
|
-
executor.execute('powershell', ['-Command', 'echo test'], allow_failure: true, shell: :powershell,
|
|
219
|
+
executor.execute('powershell', ['-Command', 'echo test'], allow_failure: true, shell: :powershell,
|
|
220
|
+
timeout: 90)
|
|
219
221
|
else
|
|
220
222
|
executor.execute('echo', ['test'], allow_failure: true, shell: shell_symbol, timeout: 90)
|
|
221
223
|
end
|
|
@@ -415,7 +417,8 @@ RSpec.describe Ukiryu::Executor do
|
|
|
415
417
|
end
|
|
416
418
|
|
|
417
419
|
it 'handles commands with special characters in arguments' do
|
|
418
|
-
result = executor.execute('echo', ['hello "quoted" test\'s'], allow_failure: true, shell: shell_symbol,
|
|
420
|
+
result = executor.execute('echo', ['hello "quoted" test\'s'], allow_failure: true, shell: shell_symbol,
|
|
421
|
+
timeout: 90)
|
|
419
422
|
|
|
420
423
|
expect(result.stdout.strip).to include('hello')
|
|
421
424
|
end
|
|
@@ -77,9 +77,10 @@ RSpec.describe Ukiryu::Models::ExecutionReport do
|
|
|
77
77
|
# In Docker/CI, may be 0 or an actual value
|
|
78
78
|
expect(stage.memory_after).to be_a(Integer)
|
|
79
79
|
expect(stage.memory_after).to be >= 0
|
|
80
|
-
#
|
|
81
|
-
expect(stage.memory_after).to be >= stage.memory_before if stage.memory_before > 0
|
|
80
|
+
# Memory delta is calculated (may be negative due to GC)
|
|
82
81
|
expect(stage.memory_delta).to be_a(Integer)
|
|
82
|
+
# Verify delta calculation is correct
|
|
83
|
+
expect(stage.memory_delta).to eq(stage.memory_after - stage.memory_before)
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
it 'marks the stage as successful by default' do
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'ukiryu/models/semantic_version'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Ukiryu::Models::SemanticVersion do
|
|
7
|
+
describe '.parse' do
|
|
8
|
+
it 'parses simple version' do
|
|
9
|
+
expect(described_class.parse('10.0')).to eq([10, 0])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'parses three-part version' do
|
|
13
|
+
expect(described_class.parse('1.2.3')).to eq([1, 2, 3])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'parses single number version' do
|
|
17
|
+
expect(described_class.parse('5')).to eq([5])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'handles nil' do
|
|
21
|
+
expect(described_class.parse(nil)).to eq([0])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'handles empty string' do
|
|
25
|
+
expect(described_class.parse('')).to eq([0])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'handles non-numeric parts as 0' do
|
|
29
|
+
expect(described_class.parse('1.alpha.3')).to eq([1, 0, 3])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '.compare' do
|
|
34
|
+
it 'returns 1 when first version is greater' do
|
|
35
|
+
expect(described_class.compare('10.0', '9.5')).to eq(1)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'returns -1 when first version is lesser' do
|
|
39
|
+
expect(described_class.compare('9.5', '10.0')).to eq(-1)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns 0 when versions are equal' do
|
|
43
|
+
expect(described_class.compare('10.0', '10.0')).to eq(0)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'handles different segment counts' do
|
|
47
|
+
expect(described_class.compare('10.0.1', '10.0')).to eq(1)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'handles single vs multi-part versions' do
|
|
51
|
+
expect(described_class.compare('10', '9.5')).to eq(1)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe '#initialize' do
|
|
56
|
+
it 'stores original string' do
|
|
57
|
+
version = described_class.new('10.0')
|
|
58
|
+
expect(version.original).to eq('10.0')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'parses segments' do
|
|
62
|
+
version = described_class.new('10.0.5')
|
|
63
|
+
expect(version.segments).to eq([10, 0, 5])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'handles integer input' do
|
|
67
|
+
version = described_class.new(10)
|
|
68
|
+
expect(version.segments).to eq([10])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'handles nil input' do
|
|
72
|
+
version = described_class.new(nil)
|
|
73
|
+
expect(version.segments).to eq([0])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#<=>' do
|
|
78
|
+
it 'compares 10.0 > 9.5 (the critical bug case)' do
|
|
79
|
+
v1 = described_class.new('10.0')
|
|
80
|
+
v2 = described_class.new('9.5')
|
|
81
|
+
expect(v1 <=> v2).to eq(1)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'compares 9.5 < 10.0' do
|
|
85
|
+
v1 = described_class.new('9.5')
|
|
86
|
+
v2 = described_class.new('10.0')
|
|
87
|
+
expect(v1 <=> v2).to eq(-1)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'compares equal versions as 0' do
|
|
91
|
+
v1 = described_class.new('10.0')
|
|
92
|
+
v2 = described_class.new('10.0')
|
|
93
|
+
expect(v1 <=> v2).to eq(0)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'compares 1.10.0 > 1.9.9' do
|
|
97
|
+
v1 = described_class.new('1.10.0')
|
|
98
|
+
v2 = described_class.new('1.9.9')
|
|
99
|
+
expect(v1 <=> v2).to eq(1)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'compares with string' do
|
|
103
|
+
version = described_class.new('10.0')
|
|
104
|
+
expect(version <=> '9.5').to eq(1)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'compares with integer' do
|
|
108
|
+
version = described_class.new('10.0')
|
|
109
|
+
expect(version <=> 9).to eq(1)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'returns nil for non-comparable' do
|
|
113
|
+
version = described_class.new('10.0')
|
|
114
|
+
expect(version <=> Object.new).to be_nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '#==' do
|
|
119
|
+
it 'returns true for equal versions' do
|
|
120
|
+
v1 = described_class.new('10.0')
|
|
121
|
+
v2 = described_class.new('10.0')
|
|
122
|
+
expect(v1 == v2).to be true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns true for equivalent versions with different segment counts' do
|
|
126
|
+
v1 = described_class.new('10.0')
|
|
127
|
+
v2 = described_class.new('10.0.0')
|
|
128
|
+
expect(v1 == v2).to be true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'returns false for different versions' do
|
|
132
|
+
v1 = described_class.new('10.0')
|
|
133
|
+
v2 = described_class.new('9.5')
|
|
134
|
+
expect(v1 == v2).to be false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'compares with string' do
|
|
138
|
+
version = described_class.new('10.0')
|
|
139
|
+
expect(version == '10.0').to be true
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe '#>' do
|
|
144
|
+
it 'returns true when greater' do
|
|
145
|
+
expect(described_class.new('10.0')).to be > described_class.new('9.5')
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'returns false when equal' do
|
|
149
|
+
expect(described_class.new('10.0')).not_to be > described_class.new('10.0')
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'returns false when lesser' do
|
|
153
|
+
expect(described_class.new('9.5')).not_to be > described_class.new('10.0')
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe '#<' do
|
|
158
|
+
it 'returns true when lesser' do
|
|
159
|
+
expect(described_class.new('9.5')).to be < described_class.new('10.0')
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'returns false when equal' do
|
|
163
|
+
expect(described_class.new('10.0')).not_to be < described_class.new('10.0')
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe '#>=' do
|
|
168
|
+
it 'returns true when greater' do
|
|
169
|
+
expect(described_class.new('10.0')).to be >= described_class.new('9.5')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'returns true when equal' do
|
|
173
|
+
expect(described_class.new('10.0')).to be >= described_class.new('10.0')
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'returns false when lesser' do
|
|
177
|
+
expect(described_class.new('9.5')).not_to be >= described_class.new('10.0')
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#<=' do
|
|
182
|
+
it 'returns true when lesser' do
|
|
183
|
+
expect(described_class.new('9.5')).to be <= described_class.new('10.0')
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'returns true when equal' do
|
|
187
|
+
expect(described_class.new('10.0')).to be <= described_class.new('10.0')
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe '#to_s' do
|
|
192
|
+
it 'returns version string' do
|
|
193
|
+
version = described_class.new('10.0.5')
|
|
194
|
+
expect(version.to_s).to eq('10.0.5')
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
describe '#inspect' do
|
|
199
|
+
it 'returns inspect string' do
|
|
200
|
+
version = described_class.new('10.0')
|
|
201
|
+
expect(version.inspect).to eq('#<Ukiryu::Models::SemanticVersion 10.0>')
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe '#hash and #eql?' do
|
|
206
|
+
it 'can be used as hash key' do
|
|
207
|
+
v1 = described_class.new('10.0')
|
|
208
|
+
v2 = described_class.new('10.0')
|
|
209
|
+
v3 = described_class.new('9.5')
|
|
210
|
+
|
|
211
|
+
hash = { v1 => 'first', v3 => 'second' }
|
|
212
|
+
|
|
213
|
+
expect(hash[v2]).to eq('first')
|
|
214
|
+
expect(hash[v3]).to eq('second')
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'eql? returns true for equal versions' do
|
|
218
|
+
v1 = described_class.new('10.0')
|
|
219
|
+
v2 = described_class.new('10.0')
|
|
220
|
+
expect(v1.eql?(v2)).to be true
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'eql? returns false for different versions' do
|
|
224
|
+
v1 = described_class.new('10.0')
|
|
225
|
+
v2 = described_class.new('9.5')
|
|
226
|
+
expect(v1.eql?(v2)).to be false
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe 'Comparable integration' do
|
|
231
|
+
it 'works with Array#max' do
|
|
232
|
+
versions = [
|
|
233
|
+
described_class.new('9.5'),
|
|
234
|
+
described_class.new('10.0'),
|
|
235
|
+
described_class.new('8.0')
|
|
236
|
+
]
|
|
237
|
+
expect(versions.max.to_s).to eq('10.0')
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'works with Array#sort' do
|
|
241
|
+
versions = [
|
|
242
|
+
described_class.new('9.5'),
|
|
243
|
+
described_class.new('10.0'),
|
|
244
|
+
described_class.new('8.0')
|
|
245
|
+
]
|
|
246
|
+
expect(versions.sort.map(&:to_s)).to eq(['8.0', '9.5', '10.0'])
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'works with Array#min' do
|
|
250
|
+
versions = [
|
|
251
|
+
described_class.new('9.5'),
|
|
252
|
+
described_class.new('10.0'),
|
|
253
|
+
described_class.new('8.0')
|
|
254
|
+
]
|
|
255
|
+
expect(versions.min.to_s).to eq('8.0')
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
describe 'real-world Ghostscript case' do
|
|
260
|
+
it 'correctly selects 10.0 over 9.5' do
|
|
261
|
+
v10 = described_class.new('10.0')
|
|
262
|
+
v95 = described_class.new('9.5')
|
|
263
|
+
|
|
264
|
+
# This was the bug: alphabetical sort would select 9.5 over 10.0
|
|
265
|
+
# because '9' > '1' in ASCII
|
|
266
|
+
expect([v95, v10].max).to eq(v10)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it 'demonstrates the bug with string comparison' do
|
|
270
|
+
# Show why string comparison is wrong
|
|
271
|
+
filenames = ['9.5.yaml', '10.0.yaml']
|
|
272
|
+
|
|
273
|
+
# WRONG: alphabetical max gives '9.5.yaml' (because '9' > '1')
|
|
274
|
+
alphabetical_max = filenames.max
|
|
275
|
+
expect(alphabetical_max).to eq('9.5.yaml')
|
|
276
|
+
|
|
277
|
+
# CORRECT: semantic version comparison gives '10.0.yaml'
|
|
278
|
+
semantic_max = filenames.max_by do |f|
|
|
279
|
+
described_class.new(File.basename(f, '.yaml'))
|
|
280
|
+
end
|
|
281
|
+
expect(semantic_max).to eq('10.0.yaml')
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe 'PowerShell Integration Tests', if: system('which pwsh > /dev/null 2>&1') do
|
|
6
|
+
let(:shell) { Ukiryu::Shell::PowerShell.new }
|
|
7
|
+
let(:env) { Ukiryu::Environment.system }
|
|
8
|
+
|
|
9
|
+
describe '#execute_command' do
|
|
10
|
+
context 'with dash-prefixed arguments (prefix stripping prevention)' do
|
|
11
|
+
it 'preserves -sDEVICE=pdfwrite style arguments' do
|
|
12
|
+
result = shell.execute_command('echo', ['-sDEVICE=pdfwrite', 'input.eps'], env, 30, nil)
|
|
13
|
+
expect(result[:status]).to eq(0)
|
|
14
|
+
# The full argument should be present (not stripped to just =pdfwrite)
|
|
15
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'preserves multiple dash-prefixed arguments' do
|
|
19
|
+
args = ['-sDEVICE=pdfwrite', '-sOutputFile=output.pdf', '-dBATCH']
|
|
20
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
21
|
+
expect(result[:status]).to eq(0)
|
|
22
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
23
|
+
expect(result[:stdout]).to include('-sOutputFile=output.pdf')
|
|
24
|
+
expect(result[:stdout]).to include('-dBATCH')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'preserves -resize style arguments' do
|
|
28
|
+
result = shell.execute_command('echo', ['-resize', '50x50'], env, 30, nil)
|
|
29
|
+
expect(result[:status]).to eq(0)
|
|
30
|
+
expect(result[:stdout]).to include('-resize')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'with single quotes in arguments' do
|
|
35
|
+
it 'correctly escapes single quotes by doubling them' do
|
|
36
|
+
result = shell.execute_command('echo', ["it's a test"], env, 30, nil)
|
|
37
|
+
expect(result[:status]).to eq(0)
|
|
38
|
+
expect(result[:stdout].strip).to eq("it's a test")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'handles multiple single quotes' do
|
|
42
|
+
result = shell.execute_command('echo', ["it's a test's value"], env, 30, nil)
|
|
43
|
+
expect(result[:status]).to eq(0)
|
|
44
|
+
expect(result[:stdout].strip).to eq("it's a test's value")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'with special characters in arguments' do
|
|
49
|
+
it 'handles dollar signs (escaped in double quotes)' do
|
|
50
|
+
result = shell.execute_command('echo', ['$VAR'], env, 30, nil)
|
|
51
|
+
expect(result[:status]).to eq(0)
|
|
52
|
+
# Dollar signs are escaped with backtick in double-quoted strings
|
|
53
|
+
expect(result[:stdout].strip).to eq('$VAR')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'handles backticks (escaped in double quotes)' do
|
|
57
|
+
result = shell.execute_command('echo', ['`hello`'], env, 30, nil)
|
|
58
|
+
expect(result[:status]).to eq(0)
|
|
59
|
+
# Backticks are escaped with backtick in double-quoted strings
|
|
60
|
+
expect(result[:stdout].strip).to eq('`hello`')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'handles arguments with spaces' do
|
|
64
|
+
result = shell.execute_command('echo', ['hello world'], env, 30, nil)
|
|
65
|
+
expect(result[:status]).to eq(0)
|
|
66
|
+
expect(result[:stdout].strip).to eq('hello world')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'handles semicolons safely' do
|
|
70
|
+
result = shell.execute_command('echo', ['hello;world'], env, 30, nil)
|
|
71
|
+
expect(result[:status]).to eq(0)
|
|
72
|
+
expect(result[:stdout].strip).to eq('hello;world')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'with executable paths' do
|
|
77
|
+
it 'handles paths with spaces' do
|
|
78
|
+
# Use /bin/echo which is a simple command that exists
|
|
79
|
+
result = shell.execute_command('/bin/echo', ['test'], env, 30, nil)
|
|
80
|
+
expect(result[:status]).to eq(0)
|
|
81
|
+
expect(result[:stdout].strip).to eq('test')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe '#join' do
|
|
87
|
+
context 'with dash-prefixed arguments' do
|
|
88
|
+
it 'quotes arguments starting with dash with double quotes' do
|
|
89
|
+
cmd = shell.join('gs', '-sDEVICE=pdfwrite', 'input.eps', 'output.pdf')
|
|
90
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'quotes multiple dash-prefixed arguments' do
|
|
94
|
+
cmd = shell.join('gs', '-sDEVICE=pdfwrite', '-dBATCH', 'input.eps')
|
|
95
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
96
|
+
expect(cmd).to include('"-dBATCH"')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'with arguments containing special characters' do
|
|
101
|
+
it 'quotes and escapes arguments with dollar signs' do
|
|
102
|
+
cmd = shell.join('echo', '$VAR')
|
|
103
|
+
# Dollar sign is escaped with backtick in double-quoted strings
|
|
104
|
+
expect(cmd).to include('"`$VAR"')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'quotes arguments with spaces' do
|
|
108
|
+
cmd = shell.join('echo', 'hello world')
|
|
109
|
+
expect(cmd).to include('"hello world"')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'with simple arguments' do
|
|
114
|
+
it 'does not quote simple arguments' do
|
|
115
|
+
cmd = shell.join('echo', 'hello', 'world')
|
|
116
|
+
expect(cmd).to eq('echo hello world')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'with executable paths containing spaces' do
|
|
121
|
+
it 'quotes the executable path' do
|
|
122
|
+
cmd = shell.join('/path with space/gs', '-sDEVICE=pdfwrite')
|
|
123
|
+
expect(cmd).to include('"/path with space/gs"')
|
|
124
|
+
expect(cmd).to include('"-sDEVICE=pdfwrite"')
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe '#execute_command_with_stdin' do
|
|
130
|
+
it 'passes stdin data correctly' do
|
|
131
|
+
result = shell.execute_command_with_stdin('cat', [], env, 30, nil, 'hello from stdin')
|
|
132
|
+
expect(result[:status]).to eq(0)
|
|
133
|
+
expect(result[:stdout].strip).to eq('hello from stdin')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'preserves dash-prefixed arguments with stdin' do
|
|
137
|
+
# Use /bin/echo instead of 'echo' because PowerShell's echo alias (Write-Output)
|
|
138
|
+
# doesn't handle stdin piping correctly on Unix
|
|
139
|
+
result = shell.execute_command_with_stdin('/bin/echo', ['-sDEVICE=pdfwrite'], env, 30, nil, '')
|
|
140
|
+
expect(result[:status]).to eq(0)
|
|
141
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe 'real-world Ghostscript command simulation' do
|
|
146
|
+
it 'formats Ghostscript-style command correctly' do
|
|
147
|
+
# Simulate the command Vectory would run
|
|
148
|
+
args = [
|
|
149
|
+
'-sDEVICE=pdfwrite',
|
|
150
|
+
'-sOutputFile=output.pdf',
|
|
151
|
+
'-dBATCH',
|
|
152
|
+
'-dNOPAUSE',
|
|
153
|
+
'input.ps'
|
|
154
|
+
]
|
|
155
|
+
result = shell.execute_command('echo', args, env, 30, nil)
|
|
156
|
+
|
|
157
|
+
expect(result[:status]).to eq(0)
|
|
158
|
+
expect(result[:stdout]).to include('-sDEVICE=pdfwrite')
|
|
159
|
+
expect(result[:stdout]).to include('-sOutputFile=output.pdf')
|
|
160
|
+
expect(result[:stdout]).to include('-dBATCH')
|
|
161
|
+
expect(result[:stdout]).to include('-dNOPAUSE')
|
|
162
|
+
expect(result[:stdout]).to include('input.ps')
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|