hrx 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,346 @@
1
+ # coding: utf-8
2
+ # Copyright 2018 Google Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'hrx'
17
+
18
+ require_relative 'validates_path'
19
+
20
+ RSpec.describe HRX, ".parse" do
21
+ it "parses an empty file" do
22
+ expect(HRX::Archive.parse("").entries).to be_empty
23
+ end
24
+
25
+ it "converts the file to UTF-8" do
26
+ hrx = HRX::Archive.parse("<===> いか\n".encode("SJIS"))
27
+ expect(hrx.entries.first.path).to be == "いか"
28
+ end
29
+
30
+ it "requires the file to be convetible to UTF-8" do
31
+ expect do
32
+ HRX::Archive.parse("<===> \xc3\x28\n".b)
33
+ end.to raise_error(EncodingError)
34
+ end
35
+
36
+ context "with a single file" do
37
+ subject {HRX::Archive.parse(<<END)}
38
+ <===> file
39
+ contents
40
+ END
41
+
42
+ it "parses one entry" do
43
+ expect(subject.entries.length).to be == 1
44
+ end
45
+
46
+ it "parses the filename" do
47
+ expect(subject.entries.first.path).to be == "file"
48
+ end
49
+
50
+ it "parses the contents" do
51
+ expect(subject.entries.first.content).to be == "contents\n"
52
+ end
53
+
54
+ it "parses contents without a newline" do
55
+ hrx = HRX::Archive.parse("<===> file\ncontents")
56
+ expect(hrx.entries.first.content).to be == "contents"
57
+ end
58
+
59
+ it "parses contents with boundary-like sequences" do
60
+ hrx = HRX::Archive.parse(<<END)
61
+ <===> file
62
+ <==>
63
+ inline <===>
64
+ <====>
65
+ END
66
+ expect(hrx.entries.first.content).to be == <<END
67
+ <==>
68
+ inline <===>
69
+ <====>
70
+ END
71
+ end
72
+
73
+ context "with a comment" do
74
+ subject {HRX::Archive.parse(<<END)}
75
+ <===>
76
+ comment
77
+ <===> file
78
+ contents
79
+ END
80
+
81
+ it "parses one entry" do
82
+ expect(subject.entries.length).to be == 1
83
+ end
84
+
85
+ it "parses the filename" do
86
+ expect(subject.entries.first.path).to be == "file"
87
+ end
88
+
89
+ it "parses the contents" do
90
+ expect(subject.entries.first.content).to be == "contents\n"
91
+ end
92
+
93
+ it "parses the comment" do
94
+ expect(subject.entries.first.comment).to be == "comment"
95
+ end
96
+ end
97
+ end
98
+
99
+ context "with multiple files" do
100
+ subject {HRX::Archive.parse(<<END)}
101
+ <===> file 1
102
+ contents 1
103
+
104
+ <===> file 2
105
+ contents 2
106
+ END
107
+
108
+ it "parses two entries" do
109
+ expect(subject.entries.length).to be == 2
110
+ end
111
+
112
+ it "parses the first filename" do
113
+ expect(subject.entries.first.path).to be == "file 1"
114
+ end
115
+
116
+ it "parses the first contents" do
117
+ expect(subject.entries.first.content).to be == "contents 1\n"
118
+ end
119
+
120
+ it "parses the second filename" do
121
+ expect(subject.entries.last.path).to be == "file 2"
122
+ end
123
+
124
+ it "parses the second contents" do
125
+ expect(subject.entries.last.content).to be == "contents 2\n"
126
+ end
127
+
128
+ it "allows an explicit parent directory" do
129
+ hrx = HRX::Archive.parse(<<END)
130
+ <===> dir/
131
+ <===> dir/file
132
+ contents
133
+ END
134
+
135
+ expect(hrx.entries.last.content).to be == "contents\n"
136
+ end
137
+
138
+ it "parses contents without a newline" do
139
+ hrx = HRX::Archive.parse(<<END)
140
+ <===> file 1
141
+ contents 1
142
+ <===> file 2
143
+ contents 2
144
+ END
145
+ expect(hrx.entries.first.content).to be == "contents 1"
146
+ end
147
+
148
+ it "parses contents with boundary-like sequences" do
149
+ hrx = HRX::Archive.parse(<<END)
150
+ <===> file 1
151
+ <==>
152
+ inline <===>
153
+ <====>
154
+
155
+ <===> file 2
156
+ contents
157
+ END
158
+ expect(hrx.entries.first.content).to be == <<END
159
+ <==>
160
+ inline <===>
161
+ <====>
162
+ END
163
+ end
164
+
165
+ context "with a comment" do
166
+ subject {HRX::Archive.parse(<<END)}
167
+ <===> file 1
168
+ contents 1
169
+
170
+ <===>
171
+ comment
172
+ <===> file 2
173
+ contents 2
174
+ END
175
+
176
+ it "parses two entries" do
177
+ expect(subject.entries.length).to be == 2
178
+ end
179
+
180
+ it "parses the first filename" do
181
+ expect(subject.entries.first.path).to be == "file 1"
182
+ end
183
+
184
+ it "parses the first contents" do
185
+ expect(subject.entries.first.content).to be == "contents 1\n"
186
+ end
187
+
188
+ it "parses the second filename" do
189
+ expect(subject.entries.last.path).to be == "file 2"
190
+ end
191
+
192
+ it "parses the second contents" do
193
+ expect(subject.entries.last.content).to be == "contents 2\n"
194
+ end
195
+
196
+ it "parses the comment" do
197
+ expect(subject.entries.last.comment).to be == "comment"
198
+ end
199
+ end
200
+ end
201
+
202
+ it "parses a file that only contains a comment" do
203
+ expect(HRX::Archive.parse(<<END).last_comment).to be == "contents\n"
204
+ <===>
205
+ contents
206
+ END
207
+ end
208
+
209
+ it "parses a file that only contains a comment with boundary-like sequences" do
210
+ expect(HRX::Archive.parse(<<HRX).last_comment).to be == <<CONTENTS
211
+ <===>
212
+ <==>
213
+ inline <===>
214
+ <====>
215
+ HRX
216
+ <==>
217
+ inline <===>
218
+ <====>
219
+ CONTENTS
220
+ end
221
+
222
+ context "with a file and a trailing comment" do
223
+ subject {HRX::Archive.parse(<<END)}
224
+ <===> file
225
+ contents
226
+
227
+ <===>
228
+ comment
229
+ END
230
+
231
+ it "parses one entry" do
232
+ expect(subject.entries.length).to be == 1
233
+ end
234
+
235
+ it "parses the filename" do
236
+ expect(subject.entries.first.path).to be == "file"
237
+ end
238
+
239
+ it "parses the contents" do
240
+ expect(subject.entries.first.content).to be == "contents\n"
241
+ end
242
+
243
+ it "parses the trailing comment" do
244
+ expect(subject.last_comment).to be == "comment\n"
245
+ end
246
+ end
247
+
248
+ context "with a single directory" do
249
+ subject {HRX::Archive.parse("<===> dir/\n")}
250
+
251
+ it "parses one entry" do
252
+ expect(subject.entries.length).to be == 1
253
+ end
254
+
255
+ it "parses a directory" do
256
+ expect(subject.entries.first).to be_a(HRX::Directory)
257
+ end
258
+
259
+ it "parses the filename" do
260
+ expect(subject.entries.first.path).to be == "dir/"
261
+ end
262
+ end
263
+
264
+ it "serializes in source order" do
265
+ hrx = HRX::Archive.parse(<<END)
266
+ <===> foo
267
+ <===> dir/bar
268
+ <===> baz
269
+ <===> dir/qux
270
+ END
271
+
272
+ expect(hrx.to_hrx).to be == <<END
273
+ <===> foo
274
+ <===> dir/bar
275
+ <===> baz
276
+ <===> dir/qux
277
+ END
278
+ end
279
+
280
+ it "serializes with the source boundary" do
281
+ hrx = HRX::Archive.parse("<=> file\n")
282
+ expect(hrx.to_hrx).to be == "<=> file\n"
283
+ end
284
+
285
+ let(:constructor) {lambda {|path| HRX::Archive.parse("<===> #{path}\n")}}
286
+ include_examples "validates paths"
287
+
288
+ context "forbids an HRX file that" do
289
+ # Specifies that the given HRX archive, with the given human-readable
290
+ # description, can't be parsed.
291
+ def self.that(description, text, message)
292
+ it description do
293
+ expect {HRX::Archive.parse(text)}.to raise_error(HRX::ParseError, message)
294
+ end
295
+ end
296
+
297
+ that "doesn't start with a boundary", "file\n", /Expected boundary/
298
+ that "starts with an unclosed boundary", "<== file\n", /Expected boundary/
299
+ that "starts with an unopened boundary", "==> file\n", /Expected boundary/
300
+ that "starts with a malformed boundary", "<> file\n", /Expected boundary/
301
+ that "has a directory with contents", "<===> dir/\ncontents", /Expected boundary/
302
+
303
+ that "has duplicate files", "<=> file\n<=> file\n", /"file" defined twice/
304
+ that "has duplicate directories", "<=> dir/\n<=> dir/\n", %r{"dir/" defined twice}
305
+ that "has file with the same name as a directory", "<=> foo/\n<=> foo\n", /"foo" defined twice/
306
+ that "has file with the same name as an earlier implicit directory", "<=> foo/bar\n<=> foo\n", /"foo" defined twice/
307
+ that "has file with the same name as a later implicit directory", "<=> foo\n<=> foo/bar\n", /"foo" defined twice/
308
+
309
+ context "has a boundary that" do
310
+ that "isn't followed by a space", "<=>file\n", /Expected space/
311
+ that "isn't followed by a path", "<=> \n", /Expected a path/
312
+ that "has a file without a newline", "<=> file", /Expected newline/
313
+ end
314
+
315
+ context "has a middle boundary that" do
316
+ that "isn't followed by a space", "<=> file 1\n<=>file 2\n", /Expected space/
317
+ that "isn't followed by a path", "<=> file 1\n<=> \n", /Expected a path/
318
+ that "has a file without a newline", "<=> file 1\n<=> file", /Expected newline/
319
+ end
320
+
321
+ context "has multiple comments that" do
322
+ that "come before a file", <<END, /Expected space/
323
+ <=>
324
+ comment 1
325
+ <=>
326
+ comment 2
327
+ <=> file
328
+ END
329
+
330
+ that "come after a file", <<END, /Expected space/
331
+ <=> file
332
+ <=>
333
+ comment 1
334
+ <=>
335
+ comment 2
336
+ END
337
+
338
+ that "appear on their own", <<END, /Expected space/
339
+ <=>
340
+ comment 1
341
+ <=>
342
+ comment 2
343
+ END
344
+ end
345
+ end
346
+ end
@@ -0,0 +1,95 @@
1
+ # Copyright 2018 Google Inc
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'stringio'
16
+
17
+ module Helpers
18
+ # Runs a block with warnings disabled.
19
+ def silence_warnings
20
+ old_stderr = $stderr
21
+ $stderr = StringIO.new
22
+ yield
23
+ ensure
24
+ $stderr = old_stderr if old_stderr
25
+ end
26
+
27
+ # Runs a block with `encoding` as Encoding.default_external.
28
+ def with_external_encoding(encoding)
29
+ old_encoding = Encoding.default_external
30
+ silence_warnings {Encoding.default_external = "iso-8859-1"}
31
+ ensure
32
+ silence_warnings {Encoding.default_external = old_encoding if old_encoding}
33
+ end
34
+ end
35
+
36
+ RSpec.configure do |config|
37
+ config.include Helpers
38
+
39
+ config.expect_with :rspec do |expectations|
40
+ # This option will default to `true` in RSpec 4. It makes the `description`
41
+ # and `failure_message` of custom matchers include text for helper methods
42
+ # defined using `chain`, e.g.:
43
+ # be_bigger_than(2).and_smaller_than(4).description
44
+ # # => "be bigger than 2 and smaller than 4"
45
+ # ...rather than:
46
+ # # => "be bigger than 2"
47
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
48
+ end
49
+
50
+ config.mock_with :rspec do |mocks|
51
+ # Prevents you from mocking or stubbing a method that does not exist on
52
+ # a real object. This is generally recommended, and will default to
53
+ # `true` in RSpec 4.
54
+ mocks.verify_partial_doubles = true
55
+ end
56
+
57
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
58
+ # have no way to turn it off -- the option exists only for backwards
59
+ # compatibility in RSpec 3). It causes shared context metadata to be
60
+ # inherited by the metadata hash of host groups and examples, rather than
61
+ # triggering implicit auto-inclusion in groups with matching metadata.
62
+ config.shared_context_metadata_behavior = :apply_to_host_groups
63
+
64
+ # Allows RSpec to persist some state between runs in order to support
65
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
66
+ # you configure your source control system to ignore this file.
67
+ config.example_status_persistence_file_path = "spec/examples.txt"
68
+
69
+ # Limits the available syntax to the non-monkey patched syntax that is
70
+ # recommended. For more details, see:
71
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
72
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
73
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
74
+ config.disable_monkey_patching!
75
+
76
+ # This setting enables warnings. It's recommended, but in some cases may
77
+ # be too noisy due to issues in dependencies.
78
+ config.warnings = true
79
+
80
+ # Many RSpec users commonly either run the entire suite or an individual
81
+ # file, and it's useful to allow more verbose output when running an
82
+ # individual spec file.
83
+ if config.files_to_run.one?
84
+ # Use the documentation formatter for detailed output,
85
+ # unless a formatter has already been configured
86
+ # (e.g. via a command-line flag).
87
+ config.default_formatter = "doc"
88
+ end
89
+
90
+ # Seed global randomization in this process using the `--seed` CLI option.
91
+ # Setting this allows you to use `--seed` to deterministically reproduce
92
+ # test failures related to randomization by passing the same `--seed` value
93
+ # as the one that triggered the failure.
94
+ Kernel.srand config.seed
95
+ end
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+ # Copyright 2018 Google Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ RSpec.shared_examples "validates paths" do
17
+ # Specifies that the given `path`, described by `description`, is allowed.
18
+ def self.allows_a_path_that(description, path)
19
+ it "allows a path that #{description}" do
20
+ expect { constructor[path] }.not_to raise_error
21
+ end
22
+ end
23
+
24
+ # Specifies that the given `path`, described by `description`, is not allowed.
25
+ def self.forbids_a_path_that(description, path)
26
+ it "forbids a path that #{description}" do
27
+ expect { constructor[path] }.to raise_error(HRX::ParseError)
28
+ end
29
+ end
30
+
31
+ allows_a_path_that "contains one component", "foo"
32
+ allows_a_path_that "contains multiple components", "foo/bar/baz"
33
+ allows_a_path_that "contains three dots", "..."
34
+ allows_a_path_that "starts with a dot", ".foo"
35
+ allows_a_path_that "contains non-alphanumeric characters", '~`!@#$%^&*()_-+= {}[]|;"\'<,>.?'
36
+ allows_a_path_that "contains non-ASCII characters", "☃"
37
+
38
+ forbids_a_path_that "is empty", ""
39
+ forbids_a_path_that 'is "."', "."
40
+ forbids_a_path_that 'is ".."', ".."
41
+ forbids_a_path_that "is only a separator", "/"
42
+ forbids_a_path_that "begins with a separator", "/foo"
43
+ forbids_a_path_that "contains multiple separators in a row", "foo//bar"
44
+ forbids_a_path_that "contains an invalid component", "foo/../bar"
45
+
46
+ [*0x00..0x09, *0x0B..0x1F, 0x3A, 0x5C, 0x7F].each do |c|
47
+ forbids_a_path_that "contains U+00#{c.to_s(16).rjust(2, "0")}", "fo#{c.chr}o"
48
+ end
49
+ end