hrx 1.0.0

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.
@@ -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