ruby-pwsh 0.10.2 → 0.11.0.rc.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/.rubocop.yml +16 -38
- data/README.md +29 -25
- data/lib/puppet/provider/dsc_base_provider/dsc_base_provider.rb +23 -20
- data/lib/pwsh/version.rb +1 -1
- data/lib/pwsh/windows_powershell.rb +2 -2
- data/lib/pwsh.rb +31 -34
- data/spec/acceptance/dsc/basic.rb +209 -0
- data/spec/acceptance/dsc/cim_instances.rb +81 -0
- data/spec/acceptance/dsc/class.rb +129 -0
- data/spec/acceptance/dsc/complex.rb +139 -0
- data/spec/acceptance/support/setup_winrm.ps1 +6 -0
- data/spec/exit-27.ps1 +1 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/puppet/provider/dsc_base_provider/dsc_base_provider_spec.rb +2084 -0
- data/spec/unit/pwsh/util_spec.rb +293 -0
- data/spec/unit/pwsh/version_spec.rb +10 -0
- data/spec/unit/pwsh/windows_powershell_spec.rb +121 -0
- data/spec/unit/pwsh_spec.rb +821 -0
- metadata +18 -22
- data/.gitattributes +0 -2
- data/.github/workflows/ci.yml +0 -109
- data/.gitignore +0 -23
- data/.pmtignore +0 -21
- data/.rspec +0 -3
- data/CHANGELOG.md +0 -204
- data/CODEOWNERS +0 -2
- data/CONTRIBUTING.md +0 -155
- data/DESIGN.md +0 -70
- data/Gemfile +0 -54
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -188
- data/design-comms.png +0 -0
- data/metadata.json +0 -82
- data/pwshlib.md +0 -92
- data/ruby-pwsh.gemspec +0 -39
@@ -0,0 +1,293 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pwsh::Util do
|
6
|
+
let(:snake_case_string) { 'this_is_a_string' }
|
7
|
+
let(:pascal_case_string) { 'ThisIsAString' }
|
8
|
+
let(:kebab_case_string) { 'this-is-a-string' }
|
9
|
+
let(:camel_case_string) { 'thisIsAString' }
|
10
|
+
let(:snake_case_hash_in_an_array) do
|
11
|
+
[
|
12
|
+
'just a string',
|
13
|
+
{
|
14
|
+
some_key: 'a value'
|
15
|
+
},
|
16
|
+
1,
|
17
|
+
{
|
18
|
+
another_key: {
|
19
|
+
nested_key: 1,
|
20
|
+
nested_array: [
|
21
|
+
1,
|
22
|
+
'another string',
|
23
|
+
{ super_nested_key: 'value' }
|
24
|
+
]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
]
|
28
|
+
end
|
29
|
+
let(:snake_case_hash) do
|
30
|
+
{
|
31
|
+
a: 1,
|
32
|
+
apple_butter: %w[a b c],
|
33
|
+
some_key_value: {
|
34
|
+
nested_key: 1,
|
35
|
+
another_nested_key: 2
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
let(:pascal_case_hash_in_an_array) do
|
40
|
+
[
|
41
|
+
'just a string',
|
42
|
+
{
|
43
|
+
SomeKey: 'a value'
|
44
|
+
},
|
45
|
+
1,
|
46
|
+
{
|
47
|
+
AnotherKey: {
|
48
|
+
NestedKey: 1,
|
49
|
+
NestedArray: [
|
50
|
+
1,
|
51
|
+
'another string',
|
52
|
+
{ SuperNestedKey: 'value' }
|
53
|
+
]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
]
|
57
|
+
end
|
58
|
+
let(:pascal_case_hash) do
|
59
|
+
{
|
60
|
+
A: 1,
|
61
|
+
AppleButter: %w[a b c],
|
62
|
+
SomeKeyValue: {
|
63
|
+
NestedKey: 1,
|
64
|
+
AnotherNestedKey: 2
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
let(:kebab_case_hash) do
|
69
|
+
{
|
70
|
+
a: 1,
|
71
|
+
'apple-butter': %w[a b c],
|
72
|
+
'some-key-value': {
|
73
|
+
'nested-key': 1,
|
74
|
+
'another-nested-key': 2
|
75
|
+
}
|
76
|
+
}
|
77
|
+
end
|
78
|
+
let(:camel_case_hash) do
|
79
|
+
{
|
80
|
+
a: 1,
|
81
|
+
appleButter: %w[a b c],
|
82
|
+
someKeyValue: {
|
83
|
+
nestedKey: 1,
|
84
|
+
anotherNestedKey: 2
|
85
|
+
}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '.invalid_directories?' do
|
90
|
+
let(:valid_path_a) { 'C:/some/folder' }
|
91
|
+
let(:valid_path_b) { 'C:/another/folder' }
|
92
|
+
let(:valid_paths) { 'C:/some/folder;C:/another/folder' }
|
93
|
+
let(:invalid_path) { 'C:/invalid/path' }
|
94
|
+
let(:mixed_paths) { 'C:/some/folder;C:/invalid/path;C:/another/folder' }
|
95
|
+
let(:empty_string) { '' }
|
96
|
+
let(:empty_members) { 'C:/some/folder;;C:/another/folder' }
|
97
|
+
|
98
|
+
it 'returns false if passed nil' do
|
99
|
+
expect(described_class.invalid_directories?(nil)).to be false
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns false if passed an empty string' do
|
103
|
+
expect(described_class.invalid_directories?('')).to be false
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns false if one valid path is provided' do
|
107
|
+
expect(described_class).to receive(:on_windows?).and_return(true)
|
108
|
+
expect(File).to receive(:directory?).with(valid_path_a).and_return(true)
|
109
|
+
expect(described_class.invalid_directories?(valid_path_a)).to be false
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns false if a collection of valid paths is provided' do
|
113
|
+
expect(described_class).to receive(:on_windows?).and_return(true)
|
114
|
+
expect(File).to receive(:directory?).with(valid_path_a).and_return(true)
|
115
|
+
expect(File).to receive(:directory?).with(valid_path_b).and_return(true)
|
116
|
+
expect(described_class.invalid_directories?(valid_paths)).to be false
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'returns true if there is only one path and it is invalid' do
|
120
|
+
expect(described_class).to receive(:on_windows?).and_return(true)
|
121
|
+
expect(File).to receive(:directory?).with(invalid_path).and_return(false)
|
122
|
+
expect(described_class.invalid_directories?(invalid_path)).to be true
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns true if the collection has on valid and one invalid member' do
|
126
|
+
expect(described_class).to receive(:on_windows?).and_return(true)
|
127
|
+
expect(File).to receive(:directory?).with(valid_path_a).and_return(true)
|
128
|
+
expect(File).to receive(:directory?).with(valid_path_b).and_return(true)
|
129
|
+
expect(File).to receive(:directory?).with(invalid_path).and_return(false)
|
130
|
+
expect(described_class.invalid_directories?(mixed_paths)).to be true
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'returns false if collection has empty members but other entries are valid' do
|
134
|
+
expect(described_class).to receive(:on_windows?).and_return(true)
|
135
|
+
expect(File).to receive(:directory?).with(valid_path_a).and_return(true)
|
136
|
+
expect(File).to receive(:directory?).with(valid_path_b).and_return(true)
|
137
|
+
allow(File).to receive(:directory?).with('')
|
138
|
+
expect(described_class.invalid_directories?(empty_members)).to be false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '.snake_case' do
|
143
|
+
it 'converts a string to snake_case' do
|
144
|
+
expect(described_class.snake_case(camel_case_string)).to eq snake_case_string
|
145
|
+
expect(described_class.snake_case(kebab_case_string)).to eq snake_case_string
|
146
|
+
expect(described_class.snake_case(pascal_case_string)).to eq snake_case_string
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '.snake_case_hash_keys' do
|
151
|
+
it 'snake_cases the keys in a passed hash' do
|
152
|
+
expect(described_class.snake_case_hash_keys(camel_case_hash)).to eq snake_case_hash
|
153
|
+
expect(described_class.snake_case_hash_keys(kebab_case_hash)).to eq snake_case_hash
|
154
|
+
expect(described_class.snake_case_hash_keys(pascal_case_hash)).to eq snake_case_hash
|
155
|
+
expect(described_class.snake_case_hash_keys(pascal_case_hash_in_an_array)).to eq snake_case_hash_in_an_array
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '.pascal_case' do
|
160
|
+
it 'converts a string to PascalCase' do
|
161
|
+
expect(described_class.pascal_case(camel_case_string)).to eq pascal_case_string
|
162
|
+
expect(described_class.pascal_case(kebab_case_string)).to eq pascal_case_string
|
163
|
+
expect(described_class.pascal_case(snake_case_string)).to eq pascal_case_string
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '.pascal_case_hash_keys' do
|
168
|
+
it 'PascalCases the keys in a passed hash' do
|
169
|
+
expect(described_class.pascal_case_hash_keys(camel_case_hash)).to eq pascal_case_hash
|
170
|
+
expect(described_class.pascal_case_hash_keys(kebab_case_hash)).to eq pascal_case_hash
|
171
|
+
expect(described_class.pascal_case_hash_keys(snake_case_hash)).to eq pascal_case_hash
|
172
|
+
expect(described_class.pascal_case_hash_keys(snake_case_hash_in_an_array)).to eq pascal_case_hash_in_an_array
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe '.symbolize_hash_keys' do
|
177
|
+
let(:array_with_string_keys_in_hashes) do
|
178
|
+
[
|
179
|
+
'just a string',
|
180
|
+
{
|
181
|
+
'some_key' => 'a value'
|
182
|
+
},
|
183
|
+
1,
|
184
|
+
{
|
185
|
+
'another_key' => {
|
186
|
+
'nested_key' => 1,
|
187
|
+
'nested_array' => [
|
188
|
+
1,
|
189
|
+
'another string',
|
190
|
+
{ 'super_nested_key' => 'value' }
|
191
|
+
]
|
192
|
+
}
|
193
|
+
}
|
194
|
+
]
|
195
|
+
end
|
196
|
+
let(:array_with_symbol_keys_in_hashes) do
|
197
|
+
[
|
198
|
+
'just a string',
|
199
|
+
{
|
200
|
+
some_key: 'a value'
|
201
|
+
},
|
202
|
+
1,
|
203
|
+
{
|
204
|
+
another_key: {
|
205
|
+
nested_key: 1,
|
206
|
+
nested_array: [
|
207
|
+
1,
|
208
|
+
'another string',
|
209
|
+
{ super_nested_key: 'value' }
|
210
|
+
]
|
211
|
+
}
|
212
|
+
}
|
213
|
+
]
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'converts all string hash keys into symbols' do
|
217
|
+
expect(described_class.symbolize_hash_keys(array_with_string_keys_in_hashes)).to eq array_with_symbol_keys_in_hashes
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '.escape_quotes' do
|
222
|
+
it 'handles single quotes' do
|
223
|
+
expect(described_class.escape_quotes("The 'Cats' go 'meow'!")).to match(/The ''Cats'' go ''meow''!/)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'handles double single quotes' do
|
227
|
+
expect(described_class.escape_quotes("The ''Cats'' go 'meow'!")).to match(/The ''''Cats'''' go ''meow''!/)
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'handles double quotes' do
|
231
|
+
expect(described_class.escape_quotes("The 'Cats' go \"meow\"!")).to match(/The ''Cats'' go "meow"!/)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'handles dollar signs' do
|
235
|
+
expect(described_class.escape_quotes('This should show $foo variable')).to match(/This should show \$foo variable/)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '.format_powershell_value' do
|
240
|
+
let(:ruby_array) { ['string', 1, :symbol, true] }
|
241
|
+
let(:powershell_array) { "@('string', 1, symbol, $true)" }
|
242
|
+
let(:ruby_hash) do
|
243
|
+
{
|
244
|
+
string: 'string',
|
245
|
+
number: 1,
|
246
|
+
symbol: :some_symbol,
|
247
|
+
boolean: true,
|
248
|
+
nested_hash: {
|
249
|
+
another_string: 'foo',
|
250
|
+
another_number: 2,
|
251
|
+
array: [1, 2, 3]
|
252
|
+
}
|
253
|
+
}
|
254
|
+
end
|
255
|
+
let(:powershell_hash) { "@{string = 'string'; number = 1; symbol = some_symbol; boolean = $true; nested_hash = @{another_string = 'foo'; another_number = 2; array = @(1, 2, 3)}}" }
|
256
|
+
|
257
|
+
it 'returns a symbol as a non-interpolated string' do
|
258
|
+
expect(described_class.format_powershell_value(:apple)).to eq('apple')
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'returns a number as a non interpolated string' do
|
262
|
+
expect(described_class.format_powershell_value(101)).to eq('101')
|
263
|
+
expect(described_class.format_powershell_value(1.1)).to eq('1.1')
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'returns boolean values as the appropriate PowerShell automatic variable' do
|
267
|
+
expect(described_class.format_powershell_value(true)).to eq('$true')
|
268
|
+
expect(described_class.format_powershell_value(:false)).to eq('$false') # rubocop:disable Lint/BooleanSymbol
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'returns a string as an escaped string' do
|
272
|
+
expect(described_class.format_powershell_value('some string')).to eq("'some string'")
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'returns an array as a string representing a PowerShell array' do
|
276
|
+
expect(described_class.format_powershell_value(ruby_array)).to eq(powershell_array)
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'returns a hash as a string representing a PowerShell hash' do
|
280
|
+
expect(described_class.format_powershell_value(ruby_hash)).to eq(powershell_hash)
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'raises an error if an unknown type is passed' do
|
284
|
+
expect { described_class.format_powershell_value(described_class) }.to raise_error(/unsupported type Module/)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe '.custom_powershell_property' do
|
289
|
+
it 'returns a powershell hash with the name and expression keys' do
|
290
|
+
expect(described_class.custom_powershell_property('apple', '$_.SomeValue / 5')).to eq("@{Name = 'apple'; Expression = {$_.SomeValue / 5}}")
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pwsh::WindowsPowerShell do
|
6
|
+
describe '.version' do
|
7
|
+
context 'on non-Windows platforms', unless: Pwsh::Util.on_windows? do
|
8
|
+
it 'is not defined' do
|
9
|
+
expect(defined?(described_class.version)).to be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'On Windows', if: Pwsh::Util.on_windows? do
|
14
|
+
context 'when Windows PowerShell version is greater than three' do
|
15
|
+
it 'detects a Windows PowerShell version' do
|
16
|
+
allow_any_instance_of(Win32::Registry).to receive(:[]).with('PowerShellVersion').and_return('5.0.10514.6')
|
17
|
+
expect(described_class.version).to eq('5.0.10514.6')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'calls the Windows PowerShell three registry path' do
|
21
|
+
reg_key = instance_double(bob)
|
22
|
+
allow(reg_key).to receive(:[]).with('PowerShellVersion').and_return('5.0.10514.6')
|
23
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).and_yield(reg_key)
|
24
|
+
|
25
|
+
described_class.version
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not call Windows PowerShell one registry path' do
|
29
|
+
reg_key = instance_double(bob)
|
30
|
+
allow(reg_key).to receive(:[]).with('PowerShellVersion').and_return('5.0.10514.6')
|
31
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).and_yield(reg_key)
|
32
|
+
expect_any_instance_of(Win32::Registry).not_to receive(:open).with('SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine', Win32::Registry::KEY_READ | 0x100)
|
33
|
+
|
34
|
+
described_class.version
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when Windows PowerShell version is less than three' do
|
39
|
+
it 'detects a Windows PowerShell version' do
|
40
|
+
allow_any_instance_of(Win32::Registry).to receive(:[]).with('PowerShellVersion').and_return('2.0')
|
41
|
+
|
42
|
+
expect(described_class.version).to eq('2.0')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'calls the Windows PowerShell one registry path' do
|
46
|
+
reg_key = instance_double(bob)
|
47
|
+
allow(reg_key).to receive(:[]).with('PowerShellVersion').and_return('2.0')
|
48
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine',
|
49
|
+
Win32::Registry::KEY_READ | 0x100).and_yield(reg_key)
|
50
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine',
|
51
|
+
Win32::Registry::KEY_READ | 0x100).and_raise(Win32::Registry::Error.new(2), 'nope')
|
52
|
+
|
53
|
+
expect(described_class.version).to eq('2.0')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when Windows PowerShell is not installed' do
|
58
|
+
it 'returns nil and not throw' do
|
59
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine',
|
60
|
+
Win32::Registry::KEY_READ | 0x100).and_raise(Win32::Registry::Error.new(2), 'nope')
|
61
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine',
|
62
|
+
Win32::Registry::KEY_READ | 0x100).and_raise(Win32::Registry::Error.new(2), 'nope')
|
63
|
+
|
64
|
+
expect(described_class.version).to be_nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '.compatible_version?' do
|
71
|
+
context 'on non-Windows platforms', unless: Pwsh::Util.on_windows? do
|
72
|
+
it 'returns false' do
|
73
|
+
expect(described_class.compatible_version?).to be(false)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'On Windows', if: Pwsh::Util.on_windows? do
|
78
|
+
context 'when the Windows PowerShell major version is nil' do
|
79
|
+
it 'returns false' do
|
80
|
+
expect(described_class).to receive(:version).and_return(nil)
|
81
|
+
expect(described_class.compatible_version?).to be(false)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when the Windows PowerShell major version is less than two' do
|
86
|
+
it 'returns false' do
|
87
|
+
expect(described_class).to receive(:version).and_return('1.0')
|
88
|
+
expect(described_class.compatible_version?).to be(false)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when the Windows PowerShell major version is two' do
|
93
|
+
it 'returns true if .NET 3.5 is installed' do
|
94
|
+
expect(described_class).to receive(:version).and_return('2.0')
|
95
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', Win32::Registry::KEY_READ | 0x100).and_yield
|
96
|
+
expect(described_class.compatible_version?).to be(true)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'returns false if .NET 3.5 is not installed' do
|
100
|
+
expect(described_class).to receive(:version).and_return('2.0')
|
101
|
+
allow_any_instance_of(Win32::Registry).to receive(:open).with('SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', Win32::Registry::KEY_READ | 0x100).and_raise(Win32::Registry::Error, 1)
|
102
|
+
expect(described_class.compatible_version?).to be(false)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when the Windows PowerShell major version is three' do
|
107
|
+
it 'returns true' do
|
108
|
+
expect(described_class).to receive(:version).and_return('3.0')
|
109
|
+
expect(described_class.compatible_version?).to be(true)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when the Windows PowerShell major version is greater than three' do
|
114
|
+
it 'returns true' do
|
115
|
+
expect(described_class).to receive(:version).and_return('4.0')
|
116
|
+
expect(described_class.compatible_version?).to be(true)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|