hocon 0.9.5 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -2
  3. data/README.md +22 -10
  4. data/lib/hocon.rb +9 -3
  5. data/lib/hocon/config_factory.rb +4 -0
  6. data/lib/hocon/config_value_factory.rb +13 -2
  7. data/lib/hocon/impl/config_reference.rb +5 -2
  8. data/lib/hocon/impl/simple_config_origin.rb +1 -1
  9. data/spec/fixtures/parse_render/example1/input.conf +21 -0
  10. data/spec/fixtures/parse_render/example1/output.conf +26 -0
  11. data/spec/fixtures/parse_render/example1/output_nocomments.conf +17 -0
  12. data/spec/fixtures/parse_render/example2/input.conf +10 -0
  13. data/spec/fixtures/parse_render/example2/output.conf +17 -0
  14. data/spec/fixtures/parse_render/example2/output_nocomments.conf +17 -0
  15. data/spec/fixtures/parse_render/example3/input.conf +2 -0
  16. data/spec/fixtures/parse_render/example3/output.conf +2 -0
  17. data/spec/fixtures/parse_render/example4/input.json +6 -0
  18. data/spec/fixtures/parse_render/example4/output.conf +6 -0
  19. data/spec/fixtures/test_utils/resources/bom.conf +2 -0
  20. data/spec/fixtures/test_utils/resources/cycle.conf +1 -0
  21. data/spec/fixtures/test_utils/resources/file-include.conf +5 -0
  22. data/spec/fixtures/test_utils/resources/include-from-list.conf +4 -0
  23. data/spec/fixtures/test_utils/resources/subdir/bar.conf +1 -0
  24. data/spec/fixtures/test_utils/resources/subdir/baz.conf +1 -0
  25. data/spec/fixtures/test_utils/resources/subdir/foo.conf +5 -0
  26. data/spec/fixtures/test_utils/resources/test01.conf +80 -0
  27. data/spec/fixtures/test_utils/resources/test01.json +4 -0
  28. data/spec/fixtures/test_utils/resources/test03.conf +36 -0
  29. data/spec/spec_helper.rb +43 -0
  30. data/spec/test_utils.rb +757 -0
  31. data/spec/unit/typesafe/config/concatenation_spec.rb +417 -0
  32. data/spec/unit/typesafe/config/conf_parser_spec.rb +822 -0
  33. data/spec/unit/typesafe/config/config_document_parser_spec.rb +494 -0
  34. data/spec/unit/typesafe/config/config_document_spec.rb +576 -0
  35. data/spec/unit/typesafe/config/config_factory_spec.rb +120 -0
  36. data/spec/unit/typesafe/config/config_node_spec.rb +552 -0
  37. data/spec/unit/typesafe/config/config_value_factory_spec.rb +85 -0
  38. data/spec/unit/typesafe/config/config_value_spec.rb +935 -0
  39. data/spec/unit/typesafe/config/hocon_spec.rb +54 -0
  40. data/spec/unit/typesafe/config/path_spec.rb +261 -0
  41. data/spec/unit/typesafe/config/public_api_spec.rb +520 -0
  42. data/spec/unit/typesafe/config/simple_config_spec.rb +112 -0
  43. data/spec/unit/typesafe/config/token_spec.rb +188 -0
  44. data/spec/unit/typesafe/config/tokenizer_spec.rb +801 -0
  45. metadata +39 -3
@@ -0,0 +1,4 @@
1
+ {
2
+ "fromJson1" : 1,
3
+ "fromJsonA" : "A"
4
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "test01" : {
3
+ "ints" : 12,
4
+ include "test01",
5
+ "booleans" : 42
6
+ },
7
+
8
+ "test02" : {
9
+ include
10
+
11
+ "test02.conf"
12
+ },
13
+
14
+ "equiv01" : {
15
+ include "equiv01/original.json"
16
+ },
17
+
18
+ # missing includes are supposed to be silently ignored
19
+ nonexistent {
20
+ include "nothere"
21
+ include "nothere.conf"
22
+ include "nothere.json"
23
+ include "nothere.properties"
24
+ }
25
+
26
+ # make sure included file substitutions fall back to parent file,
27
+ # both when the include is at the root (so doesn't need to have
28
+ # substitutions adjusted) and when it is not.
29
+ foo="This is in the including file"
30
+ bar="This is in the including file"
31
+ include "test03-included.conf"
32
+
33
+ subtree {
34
+ include "test03-included.conf"
35
+ }
36
+ }
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures")
4
+
5
+ EXAMPLE1 = { :hash =>
6
+ {"foo" => {
7
+ "bar" => {
8
+ "baz" => 42,
9
+ "abracadabra" => "hi",
10
+ "yahoo" => "yippee",
11
+ "boom" => [1, 2, {"derp" => "duh"}, 4],
12
+ "empty" => [],
13
+ "truthy" => true,
14
+ "falsy" => false
15
+ }}},
16
+ :name => "example1",
17
+ }
18
+
19
+ EXAMPLE2 = { :hash =>
20
+ {"jruby-puppet"=> {
21
+ "jruby-pools" => [{"environment" => "production"}],
22
+ "load-path" => ["/usr/lib/ruby/site_ruby/1.8", "/usr/lib/ruby/site_ruby/1.8"],
23
+ "master-conf-dir" => "/etc/puppet",
24
+ "master-var-dir" => "/var/lib/puppet",
25
+ },
26
+ "webserver" => {"host" => "1.2.3.4"}},
27
+ :name => "example2",
28
+ }
29
+
30
+ EXAMPLE3 = { :hash =>
31
+ {"a" => true,
32
+ "b" => true},
33
+ :name => "example3",
34
+ }
35
+
36
+ EXAMPLE4 = { :hash =>
37
+ {"kermit" => "frog",
38
+ "miss" => "piggy",
39
+ "bert" => "ernie",
40
+ "janice" => "guitar"},
41
+ :name => "example4",
42
+ }
43
+
@@ -0,0 +1,757 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'spec_helper'
5
+ require 'rspec'
6
+ require 'hocon/impl/config_reference'
7
+ require 'hocon/impl/substitution_expression'
8
+ require 'hocon/impl/path_parser'
9
+ require 'hocon/impl/config_impl_util'
10
+ require 'hocon/impl/config_node_simple_value'
11
+ require 'hocon/impl/config_node_single_token'
12
+ require 'hocon/impl/config_node_object'
13
+ require 'hocon/impl/config_node_array'
14
+ require 'hocon/impl/config_node_concatenation'
15
+
16
+ module TestUtils
17
+ Tokens = Hocon::Impl::Tokens
18
+ ConfigInt = Hocon::Impl::ConfigInt
19
+ ConfigDouble = Hocon::Impl::ConfigDouble
20
+ ConfigString = Hocon::Impl::ConfigString
21
+ ConfigNull = Hocon::Impl::ConfigNull
22
+ ConfigBoolean = Hocon::Impl::ConfigBoolean
23
+ ConfigReference = Hocon::Impl::ConfigReference
24
+ SubstitutionExpression = Hocon::Impl::SubstitutionExpression
25
+ ConfigConcatenation = Hocon::Impl::ConfigConcatenation
26
+ Path = Hocon::Impl::Path
27
+ EOF = Hocon::Impl::TokenType::EOF
28
+
29
+ include RSpec::Matchers
30
+
31
+ def self.intercept(exception_type, & block)
32
+ thrown = nil
33
+ result = nil
34
+ begin
35
+ result = block.call
36
+ rescue => e
37
+ if e.is_a?(exception_type)
38
+ thrown = e
39
+ else
40
+ raise "Expected exception #{exception_type} was not thrown, got #{e.class}: #{e}\n#{e.backtrace.join("\n")}"
41
+ end
42
+ end
43
+ if thrown.nil?
44
+ raise "Expected exception #{exception_type} was not thrown, no exception was thrown and got result #{result}"
45
+ end
46
+ thrown
47
+ end
48
+
49
+ class ParseTest
50
+
51
+ def self.from_s(test)
52
+ ParseTest.new(false, false, test)
53
+ end
54
+
55
+ def self.from_pair(lift_behavior_unexpected, test)
56
+ ParseTest.new(lift_behavior_unexpected, false, test)
57
+ end
58
+
59
+ def initialize(lift_behavior_unexpected, whitespace_matters, test)
60
+ @lift_behavior_unexpected = lift_behavior_unexpected
61
+ @whitespace_matters = whitespace_matters
62
+ @test = test
63
+ end
64
+ attr_reader :test
65
+
66
+ def lift_behavior_unexpected?
67
+ @lift_behavior_unexpected
68
+ end
69
+
70
+ def whitespace_matters?
71
+ @whitespace_matters
72
+ end
73
+ end
74
+
75
+
76
+ # note: it's important to put {} or [] at the root if you
77
+ # want to test "invalidity reasons" other than "wrong root"
78
+ InvalidJsonInvalidConf = [
79
+ ParseTest.from_s("{"),
80
+ ParseTest.from_s("}"),
81
+ ParseTest.from_s("["),
82
+ ParseTest.from_s("]"),
83
+ ParseTest.from_s(","),
84
+ ParseTest.from_pair(true, "10"), # value not in array or object, lift-json now allows this
85
+ ParseTest.from_pair(true, "\"foo\""), # value not in array or object, lift-json allows it
86
+ ParseTest.from_s(")\""), # single quote by itself
87
+ ParseTest.from_pair(true, "[,]"), # array with just a comma in it; lift is OK with this
88
+ ParseTest.from_pair(true, "[,,]"), # array with just two commas in it; lift is cool with this too
89
+ ParseTest.from_pair(true, "[1,2,,]"), # array with two trailing commas
90
+ ParseTest.from_pair(true, "[,1,2]"), # array with initial comma
91
+ ParseTest.from_pair(true, "{ , }"), # object with just a comma in it
92
+ ParseTest.from_pair(true, "{ , , }"), # object with just two commas in it
93
+ ParseTest.from_s("{ 1,2 }"), # object with single values not key-value pair
94
+ ParseTest.from_pair(true, '{ , "foo" : 10 }'), # object starts with comma
95
+ ParseTest.from_pair(true, "{ \"foo\" : 10 ,, }"), # object has two trailing commas
96
+ ParseTest.from_s(") \"a\" : 10 ,, "), # two trailing commas for braceless root object
97
+ ParseTest.from_s("{ \"foo\" : }"), # no value in object
98
+ ParseTest.from_s("{ : 10 }"), # no key in object
99
+ ParseTest.from_pair(true, " \"foo\" : "), # no value in object with no braces; lift-json thinks this is acceptable
100
+ ParseTest.from_pair(true, " : 10 "), # no key in object with no braces; lift-json is cool with this too
101
+ ParseTest.from_s(') "foo" : 10 } '), # close brace but no open
102
+ ParseTest.from_s(") \"foo\" : 10 } "), # close brace but no open
103
+ ParseTest.from_s(") \"foo\" : 10 [ "), # no-braces object with trailing gunk
104
+ ParseTest.from_s("{ \"foo\" }"), # no value or colon
105
+ ParseTest.from_s("{ \"a\" : [ }"), # [ is not a valid value
106
+ ParseTest.from_s("{ \"foo\" : 10, true }"), # non-key after comma
107
+ ParseTest.from_s("{ foo \n bar : 10 }"), # newline in the middle of the unquoted key
108
+ ParseTest.from_s("[ 1, \\"), # ends with backslash
109
+ # these two problems are ignored by the lift tokenizer
110
+ ParseTest.from_s("[:\"foo\", \"bar\"]"), # colon in an array; lift doesn't throw (tokenizer erases it)
111
+ ParseTest.from_s("[\"foo\" : \"bar\"]"), # colon in an array another way, lift ignores (tokenizer erases it)
112
+ ParseTest.from_s("[ \"hello ]"), # unterminated string
113
+ ParseTest.from_pair(true, "{ \"foo\" , true }"), # comma instead of colon, lift is fine with this
114
+ ParseTest.from_pair(true, "{ \"foo\" : true \"bar\" : false }"), # missing comma between fields, lift fine with this
115
+ ParseTest.from_s("[ 10, }]"), # array with } as an element
116
+ ParseTest.from_s("[ 10, {]"), # array with { as an element
117
+ ParseTest.from_s("{}x"), # trailing invalid token after the root object
118
+ ParseTest.from_s("[]x"), # trailing invalid token after the root array
119
+ ParseTest.from_pair(true, "{}{}"), # trailing token after the root object - lift OK with it
120
+ ParseTest.from_pair(true, "{}true"), # trailing token after the root object; lift ignores the {}
121
+ ParseTest.from_pair(true, "[]{}"), # trailing valid token after the root array
122
+ ParseTest.from_pair(true, "[]true"), # trailing valid token after the root array, lift ignores the []
123
+ ParseTest.from_s("[${]"), # unclosed substitution
124
+ ParseTest.from_s("[$]"), # '$' by itself
125
+ ParseTest.from_s("[$ ]"), # '$' by itself with spaces after
126
+ ParseTest.from_s("[${}]"), # empty substitution (no path)
127
+ ParseTest.from_s("[${?}]"), # no path with ? substitution
128
+ ParseTest.new(false, true, "[${ ?foo}]"), # space before ? not allowed
129
+ ParseTest.from_s(%q|{ "a" : [1,2], "b" : y${a}z }|), # trying to interpolate an array in a string
130
+ ParseTest.from_s(%q|{ "a" : { "c" : 2 }, "b" : y${a}z }|), # trying to interpolate an object in a string
131
+ ParseTest.from_s(%q|{ "a" : ${a} }|), # simple cycle
132
+ ParseTest.from_s(%q|[ { "a" : 2, "b" : ${${a}} } ]|), # nested substitution
133
+ ParseTest.from_s("[ = ]"), # = is not a valid token in unquoted text
134
+ ParseTest.from_s("[ + ]"),
135
+ ParseTest.from_s("[ # ]"),
136
+ ParseTest.from_s("[ ` ]"),
137
+ ParseTest.from_s("[ ^ ]"),
138
+ ParseTest.from_s("[ ? ]"),
139
+ ParseTest.from_s("[ ! ]"),
140
+ ParseTest.from_s("[ @ ]"),
141
+ ParseTest.from_s("[ * ]"),
142
+ ParseTest.from_s("[ & ]"),
143
+ ParseTest.from_s("[ \\ ]"),
144
+ ParseTest.from_s("+="),
145
+ ParseTest.from_s("[ += ]"),
146
+ ParseTest.from_s("+= 10"),
147
+ ParseTest.from_s("10 +="),
148
+ ParseTest.from_s("[ 10e+3e ]"), # "+" not allowed in unquoted strings, and not a valid number
149
+ ParseTest.from_pair(true, "[ \"foo\nbar\" ]"), # unescaped newline in quoted string, lift doesn't care
150
+ ParseTest.from_s("[ # comment ]"),
151
+ ParseTest.from_s("${ #comment }"),
152
+ ParseTest.from_s("[ // comment ]"),
153
+ ParseTest.from_s("${ // comment }"),
154
+ # ParseTest.from_s("{ include \"bar\" : 10 }"), # include with a value after it
155
+ ParseTest.from_s("{ include foo }"), # include with unquoted string
156
+ ParseTest.from_s("{ include : { \"a\" : 1 } }"), # include used as unquoted key
157
+ ParseTest.from_s("a="), # no value
158
+ ParseTest.from_s("a:"), # no value with colon
159
+ ParseTest.from_s("a= "), # no value with whitespace after
160
+ ParseTest.from_s("a.b="), # no value with path
161
+ ParseTest.from_s("{ a= }"), # no value inside braces
162
+ ParseTest.from_s("{ a: }") # no value with colon inside braces
163
+ ]
164
+
165
+ # We'll automatically try each of these with whitespace modifications
166
+ # so no need to add every possible whitespace variation
167
+ ValidJson = [
168
+ ParseTest.from_s("{}"),
169
+ ParseTest.from_s("[]"),
170
+ ParseTest.from_s(%q|{ "foo" : "bar" }|),
171
+ ParseTest.from_s(%q|["foo", "bar"]|),
172
+ ParseTest.from_s(%q|{ "foo" : 42 }|),
173
+ ParseTest.from_s("{ \"foo\"\n : 42 }"), # newline after key
174
+ ParseTest.from_s("{ \"foo\" : \n 42 }"), # newline after colon
175
+ ParseTest.from_s(%q|[10, 11]|),
176
+ ParseTest.from_s(%q|[10,"foo"]|),
177
+ ParseTest.from_s(%q|{ "foo" : "bar", "baz" : "boo" }|),
178
+ ParseTest.from_s(%q|{ "foo" : { "bar" : "baz" }, "baz" : "boo" }|),
179
+ ParseTest.from_s(%q|{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : "boo" }|),
180
+ ParseTest.from_s(%q|{ "foo" : [10,11,12], "baz" : "boo" }|),
181
+ ParseTest.from_s(%q|[{},{},{},{}]|),
182
+ ParseTest.from_s(%q|[[[[[[]]]]]]|),
183
+ ParseTest.from_s(%q|[[1], [1,2], [1,2,3], []]|), # nested multiple-valued array
184
+ ParseTest.from_s(%q|{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":42}}}}}}}}|),
185
+ ParseTest.from_s("[ \"#comment\" ]"), # quoted # comment
186
+ ParseTest.from_s("[ \"//comment\" ]"), # quoted // comment
187
+ # this long one is mostly to test rendering
188
+ ParseTest.from_s(%q|{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : { "bar" : "baz", "woo" : [1,2,3,4], "w00t" : true, "a" : false, "b" : 3.14, "c" : null } }|),
189
+ ParseTest.from_s("{}"),
190
+ ParseTest.from_pair(true, "[ 10e+3 ]") # "+" in a number (lift doesn't handle))
191
+ ]
192
+
193
+ ValidConfInvalidJson = [
194
+ ParseTest.from_s(""), # empty document
195
+ ParseTest.from_s(" "), # empty document single space
196
+ ParseTest.from_s("\n"), # empty document single newline
197
+ ParseTest.from_s(" \n \n \n\n\n"), # complicated empty document
198
+ ParseTest.from_s("# foo"), # just a comment
199
+ ParseTest.from_s("# bar\n"), # just a comment with a newline
200
+ ParseTest.from_s("# foo\n//bar"), # comment then another with no newline
201
+ ParseTest.from_s(%q|{ "foo" = 42 }|), # equals rather than colon
202
+ ParseTest.from_s(%q|{ foo { "bar" : 42 } }|), # omit the colon for object value
203
+ ParseTest.from_s(%q|{ foo baz { "bar" : 42 } }|), # omit the colon with unquoted key with spaces
204
+ ParseTest.from_s(%q| "foo" : 42 |), # omit braces on root object
205
+ ParseTest.from_s(%q|{ "foo" : bar }|), # no quotes on value
206
+ ParseTest.from_s(%q|{ "foo" : null bar 42 baz true 3.14 "hi" }|), # bunch of values to concat into a string
207
+ ParseTest.from_s("{ foo : \"bar\" }"), # no quotes on key
208
+ ParseTest.from_s("{ foo : bar }"), # no quotes on key or value
209
+ ParseTest.from_s("{ foo.bar : bar }"), # path expression in key
210
+ ParseTest.from_s("{ foo.\"hello world\".baz : bar }"), # partly-quoted path expression in key
211
+ ParseTest.from_s("{ foo.bar \n : bar }"), # newline after path expression in key
212
+ ParseTest.from_s("{ foo bar : bar }"), # whitespace in the key
213
+ ParseTest.from_s("{ true : bar }"), # key is a non-string token
214
+ ParseTest.from_pair(true, %q|{ "foo" : "bar", "foo" : "bar2" }|), # dup keys - lift just returns both
215
+ ParseTest.from_pair(true, "[ 1, 2, 3, ]"), # single trailing comma (lift fails to throw)
216
+ ParseTest.from_pair(true, "[1,2,3 , ]"), # single trailing comma with whitespace
217
+ ParseTest.from_pair(true, "[1,2,3\n\n , \n]"), # single trailing comma with newlines
218
+ ParseTest.from_pair(true, "[1,]"), # single trailing comma with one-element array
219
+ ParseTest.from_pair(true, "{ \"foo\" : 10, }"), # extra trailing comma (lift fails to throw)
220
+ ParseTest.from_pair(true, "{ \"a\" : \"b\", }"), # single trailing comma in object
221
+ ParseTest.from_s("{ a : b, }"), # single trailing comma in object (unquoted strings)
222
+ ParseTest.from_s("{ a : b \n , \n }"), # single trailing comma in object with newlines
223
+ ParseTest.from_s("a : b, c : d,"), # single trailing comma in object with no root braces
224
+ ParseTest.from_s("{ a : b\nc : d }"), # skip comma if there's a newline
225
+ ParseTest.from_s("a : b\nc : d"), # skip comma if there's a newline and no root braces
226
+ ParseTest.from_s("a : b\nc : d,"), # skip one comma but still have one at the end
227
+ ParseTest.from_s("[ foo ]"), # not a known token in JSON
228
+ ParseTest.from_s("[ t ]"), # start of "true" but ends wrong in JSON
229
+ ParseTest.from_s("[ tx ]"),
230
+ ParseTest.from_s("[ tr ]"),
231
+ ParseTest.from_s("[ trx ]"),
232
+ ParseTest.from_s("[ tru ]"),
233
+ ParseTest.from_s("[ trux ]"),
234
+ ParseTest.from_s("[ truex ]"),
235
+ ParseTest.from_s("[ 10x ]"), # number token with trailing junk
236
+ ParseTest.from_s("[ / ]"), # unquoted string "slash"
237
+ ParseTest.from_s("{ include \"foo\" }"), # valid include
238
+ ParseTest.from_s("{ include\n\"foo\" }"), # include with just a newline separating from string
239
+ ParseTest.from_s("{ include\"foo\" }"), # include with no whitespace after it
240
+ ParseTest.from_s("[ include ]"), # include can be a string value in an array
241
+ ParseTest.from_s("{ foo : include }"), # include can be a field value also
242
+ ParseTest.from_s("{ include \"foo\", \"a\" : \"b\" }"), # valid include followed by comma and field
243
+ ParseTest.from_s("{ foo include : 42 }"), # valid to have a key not starting with include
244
+ ParseTest.from_s("[ ${foo} ]"),
245
+ ParseTest.from_s("[ ${?foo} ]"),
246
+ ParseTest.from_s("[ ${\"foo\"} ]"),
247
+ ParseTest.from_s("[ ${foo.bar} ]"),
248
+ ParseTest.from_s("[ abc xyz ${foo.bar} qrs tuv ]"), # value concatenation
249
+ ParseTest.from_s("[ 1, 2, 3, blah ]"),
250
+ ParseTest.from_s("[ ${\"foo.bar\"} ]"),
251
+ ParseTest.from_s("{} # comment"),
252
+ ParseTest.from_s("{} // comment"),
253
+ ParseTest.from_s(%q|{ "foo" #comment
254
+ : 10 }|),
255
+ ParseTest.from_s(%q|{ "foo" // comment
256
+ : 10 }|),
257
+ ParseTest.from_s(%q|{ "foo" : #comment
258
+ 10 }|),
259
+ ParseTest.from_s(%q|{ "foo" : // comment
260
+ 10 }|),
261
+ ParseTest.from_s(%q|{ "foo" : 10 #comment
262
+ }|),
263
+ ParseTest.from_s(%q|{ "foo" : 10 // comment
264
+ }|),
265
+ ParseTest.from_s(%q|[ 10, # comment
266
+ 11]|),
267
+ ParseTest.from_s(%q|[ 10, // comment
268
+ 11]|),
269
+ ParseTest.from_s(%q|[ 10 # comment
270
+ , 11]|),
271
+ ParseTest.from_s(%q|[ 10 // comment
272
+ , 11]|),
273
+ ParseTest.from_s(%q|{ /a/b/c : 10 }|), # key has a slash in it
274
+ ParseTest.new(false, true, "[${ foo.bar}]"), # substitution with leading spaces
275
+ ParseTest.new(false, true, "[${foo.bar }]"), # substitution with trailing spaces
276
+ ParseTest.new(false, true, "[${ \"foo.bar\"}]"), # substitution with leading spaces and quoted
277
+ ParseTest.new(false, true, "[${\"foo.bar\" }]"), # substitution with trailing spaces and quoted
278
+ ParseTest.from_s(%q|[ ${"foo""bar"} ]|), # multiple strings in substitution
279
+ ParseTest.from_s(%q|[ ${foo "bar" baz} ]|), # multiple strings and whitespace in substitution
280
+ ParseTest.from_s("[${true}]"), # substitution with unquoted true token
281
+ ParseTest.from_s("a = [], a += b"), # += operator with previous init
282
+ ParseTest.from_s("{ a = [], a += 10 }"), # += in braces object with previous init
283
+ ParseTest.from_s("a += b"), # += operator without previous init
284
+ ParseTest.from_s("{ a += 10 }"), # += in braces object without previous init
285
+ ParseTest.from_s("[ 10e3e3 ]"), # two exponents. this should parse to a number plus string "e3"
286
+ ParseTest.from_s("[ 1-e3 ]"), # malformed number should end up as a string instead
287
+ ParseTest.from_s("[ 1.0.0 ]"), # two decimals, should end up as a string
288
+ ParseTest.from_s("[ 1.0. ]")
289
+ ]
290
+
291
+
292
+ InvalidConf = InvalidJsonInvalidConf
293
+
294
+ # .conf is a superset of JSON so validJson just goes in here
295
+ ValidConf = ValidConfInvalidJson + ValidJson
296
+
297
+ def self.add_offending_json_to_exception(parser_name, s, & block)
298
+ begin
299
+ block.call
300
+ rescue => e
301
+ tokens =
302
+ begin
303
+ "tokens: " + TestUtils.tokenize_as_list(s).join("\n")
304
+ rescue => tokenize_ex
305
+ "tokenizer failed: #{tokenize_ex}\n#{tokenize_ex.backtrace.join("\n")}"
306
+ end
307
+ raise ArgumentError, "#{parser_name} parser did wrong thing on '#{s}', #{tokens}; error: #{e}\n#{e.backtrace.join("\n")}"
308
+ end
309
+ end
310
+
311
+ def self.whitespace_variations(tests, valid_in_lift)
312
+ variations = [
313
+ Proc.new { |s| s }, # identity
314
+ Proc.new { |s| " " + s },
315
+ Proc.new { |s| s + " " },
316
+ Proc.new { |s| " " + s + " " },
317
+ Proc.new { |s| s.gsub(" ", "") }, # this would break with whitespace in a key or value
318
+ Proc.new { |s| s.gsub(":", " : ") }, # could break with : in a key or value
319
+ Proc.new { |s| s.gsub(",", " , ") }, # could break with , in a key or value
320
+ ]
321
+ tests.map { |t|
322
+ if t.whitespace_matters?
323
+ t
324
+ else
325
+ with_no_ascii =
326
+ if t.test.include?(" ")
327
+ [ParseTest.from_pair(valid_in_lift,
328
+ t.test.gsub(" ", "\u2003"))] # 2003 = em space, to test non-ascii whitespace
329
+ else
330
+ []
331
+ end
332
+
333
+ with_no_ascii << variations.reduce([]) { |acc, v|
334
+ acc << ParseTest.from_pair(t.lift_behavior_unexpected?, v.call(t.test))
335
+ acc
336
+ }
337
+ end
338
+ }.flatten
339
+ end
340
+
341
+
342
+ ##################
343
+ # Tokenizer Functions
344
+ ##################
345
+ def self.wrap_tokens(token_list)
346
+ # Wraps token_list in START and EOF tokens
347
+ [Tokens::START] + token_list + [Tokens::EOF]
348
+ end
349
+
350
+ def self.tokenize(config_origin, input)
351
+ Hocon::Impl::Tokenizer.tokenize(config_origin, input, Hocon::ConfigSyntax::CONF)
352
+ end
353
+
354
+ def self.tokenize_from_s(s)
355
+ tokenize(Hocon::Impl::SimpleConfigOrigin.new_simple("anonymous Reader"),
356
+ StringIO.new(s))
357
+ end
358
+
359
+ def self.tokenize_as_list(input_string)
360
+ token_iterator = tokenize_from_s(input_string)
361
+
362
+ token_iterator.to_list
363
+ end
364
+
365
+ def self.tokenize_as_string(input_string)
366
+ Hocon::Impl::Tokenizer.render(tokenize_from_s(input_string))
367
+ end
368
+
369
+ def self.config_node_simple_value(value)
370
+ Hocon::Impl::ConfigNodeSimpleValue.new(value)
371
+ end
372
+
373
+ def self.config_node_key(path)
374
+ Hocon::Impl::PathParser.parse_path_node(path)
375
+ end
376
+
377
+ def self.config_node_single_token(value)
378
+ Hocon::Impl::ConfigNodeSingleToken.new(value)
379
+ end
380
+
381
+ def self.config_node_object(nodes)
382
+ Hocon::Impl::ConfigNodeObject.new(nodes)
383
+ end
384
+
385
+ def self.config_node_array(nodes)
386
+ Hocon::Impl::ConfigNodeArray.new(nodes)
387
+ end
388
+
389
+ def self.config_node_concatenation(nodes)
390
+ Hocon::Impl::ConfigNodeConcatenation.new(nodes)
391
+ end
392
+
393
+ def self.node_colon
394
+ Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COLON)
395
+ end
396
+
397
+ def self.node_space
398
+ Hocon::Impl::ConfigNodeSingleToken.new(token_unquoted(" "))
399
+ end
400
+
401
+ def self.node_open_brace
402
+ Hocon::Impl::ConfigNodeSingleToken.new(Tokens::OPEN_CURLY)
403
+ end
404
+
405
+ def self.node_close_brace
406
+ Hocon::Impl::ConfigNodeSingleToken.new(Tokens::CLOSE_CURLY)
407
+ end
408
+
409
+ def self.node_open_bracket
410
+ Hocon::Impl::ConfigNodeSingleToken.new(Tokens::OPEN_SQUARE)
411
+ end
412
+
413
+ def self.node_close_bracket
414
+ Hocon::Impl::ConfigNodeSingleToken.new(Tokens::CLOSE_SQUARE)
415
+ end
416
+
417
+ def self.node_comma
418
+ Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COMMA)
419
+ end
420
+
421
+ def self.node_line(line)
422
+ Hocon::Impl::ConfigNodeSingleToken.new(token_line(line))
423
+ end
424
+
425
+ def self.node_whitespace(whitespace)
426
+ Hocon::Impl::ConfigNodeSingleToken.new(token_whitespace(whitespace))
427
+ end
428
+
429
+ def self.node_key_value_pair(key, value)
430
+ nodes = [key, node_space, node_colon, node_space, value]
431
+ Hocon::Impl::ConfigNodeField.new(nodes)
432
+ end
433
+
434
+ def self.node_int(value)
435
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_int(value))
436
+ end
437
+
438
+ def self.node_string(value)
439
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_string(value))
440
+ end
441
+
442
+ def self.node_double(value)
443
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_double(value))
444
+ end
445
+
446
+ def self.node_true
447
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_true)
448
+ end
449
+
450
+ def self.node_false
451
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_false)
452
+ end
453
+
454
+ def self.node_comment_hash(text)
455
+ Hocon::Impl::ConfigNodeComment.new(token_comment_hash(text))
456
+ end
457
+
458
+ def self.node_comment_double_slash(text)
459
+ Hocon::Impl::ConfigNodeComment.new(token_comment_double_slash(text))
460
+ end
461
+
462
+ def self.node_unquoted_text(text)
463
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_unquoted(text))
464
+ end
465
+
466
+ def self.node_null
467
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_null)
468
+ end
469
+
470
+ def self.node_key_substitution(s)
471
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_key_substitution(s))
472
+ end
473
+
474
+ def self.node_optional_substitution(*expression)
475
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_optional_substitution(*expression))
476
+ end
477
+
478
+ def self.node_substitution(*expression)
479
+ Hocon::Impl::ConfigNodeSimpleValue.new(token_substitution(*expression))
480
+ end
481
+
482
+ def self.fake_origin
483
+ Hocon::Impl::SimpleConfigOrigin.new_simple("fake origin")
484
+ end
485
+
486
+ def self.token_line(line_number)
487
+ Tokens.new_line(fake_origin.with_line_number(line_number))
488
+ end
489
+
490
+ def self.token_true
491
+ Tokens.new_boolean(fake_origin, true)
492
+ end
493
+
494
+ def self.token_false
495
+ Tokens.new_boolean(fake_origin, false)
496
+ end
497
+
498
+ def self.token_null
499
+ Tokens.new_null(fake_origin)
500
+ end
501
+
502
+ def self.token_unquoted(value)
503
+ Tokens.new_unquoted_text(fake_origin, value)
504
+ end
505
+
506
+ def self.token_comment_double_slash(value)
507
+ Tokens.new_comment_double_slash(fake_origin, value)
508
+ end
509
+
510
+ def self.token_comment_hash(value)
511
+ Tokens.new_comment_hash(fake_origin, value)
512
+ end
513
+
514
+ def self.token_whitespace(value)
515
+ Tokens.new_ignored_whitespace(fake_origin, value)
516
+ end
517
+
518
+ def self.token_string(value)
519
+ Tokens.new_string(fake_origin, value, "\"#{value}\"")
520
+ end
521
+
522
+ def self.token_double(value)
523
+ Tokens.new_double(fake_origin, value, "#{value}")
524
+ end
525
+
526
+ def self.token_int(value)
527
+ Tokens.new_int(fake_origin, value, "#{value}")
528
+ end
529
+
530
+ def self.token_maybe_optional_substitution(optional, token_list)
531
+ Tokens.new_substitution(fake_origin, optional, token_list)
532
+ end
533
+
534
+ def self.token_substitution(*token_list)
535
+ token_maybe_optional_substitution(false, token_list)
536
+ end
537
+
538
+ def self.token_optional_substitution(*token_list)
539
+ token_maybe_optional_substitution(true, token_list)
540
+ end
541
+
542
+ def self.token_key_substitution(value)
543
+ token_substitution(token_string(value))
544
+ end
545
+
546
+ def self.parse_object(s)
547
+ parse_config(s).root
548
+ end
549
+
550
+ def self.parse_config(s)
551
+ options = Hocon::ConfigParseOptions.defaults.
552
+ set_origin_description("test string").
553
+ set_syntax(Hocon::ConfigSyntax::CONF)
554
+ Hocon::ConfigFactory.parse_string(s, options)
555
+ end
556
+
557
+ ##################
558
+ # ConfigValue helpers
559
+ ##################
560
+ def self.int_value(value)
561
+ ConfigInt.new(fake_origin, value, nil)
562
+ end
563
+
564
+ def self.double_value(value)
565
+ ConfigDouble.new(fake_origin, value, nil)
566
+ end
567
+
568
+ def self.string_value(value)
569
+ ConfigString::Quoted.new(fake_origin, value)
570
+ end
571
+
572
+ def self.null_value
573
+ ConfigNull.new(fake_origin)
574
+ end
575
+
576
+ def self.bool_value(value)
577
+ ConfigBoolean.new(fake_origin, value)
578
+ end
579
+
580
+ def self.config_map(input_map)
581
+ # Turns {String: Int} maps into {String: ConfigInt} maps
582
+ Hash[ input_map.map { |k, v| [k, int_value(v)] } ]
583
+ end
584
+
585
+ def self.subst(ref, optional = false)
586
+ path = Path.new_path(ref)
587
+ ConfigReference.new(fake_origin, SubstitutionExpression.new(path, optional))
588
+ end
589
+
590
+ def self.subst_in_string(ref, optional = false)
591
+ pieces = [string_value("start<"), subst(ref, optional), string_value(">end")]
592
+ ConfigConcatenation.new(fake_origin, pieces)
593
+ end
594
+
595
+ ##################
596
+ # Token Functions
597
+ ##################
598
+ class NotEqualToAnythingElse
599
+ def ==(other)
600
+ other.is_a? NotEqualToAnythingElse
601
+ end
602
+
603
+ def hash
604
+ 971
605
+ end
606
+ end
607
+
608
+ ##################
609
+ # Path Functions
610
+ ##################
611
+ def self.path(*elements)
612
+ # this is importantly NOT using Path.newPath, which relies on
613
+ # the parser; in the test suite we are often testing the parser,
614
+ # so we don't want to use the parser to build the expected result.
615
+ Path.from_string_list(elements)
616
+ end
617
+
618
+ RESOURCE_DIR = "spec/fixtures/test_utils/resources"
619
+
620
+ def self.resource_file(filename)
621
+ File.join(RESOURCE_DIR, filename)
622
+ end
623
+
624
+ def self.json_quoted_resource_file(filename)
625
+ quote_json_string(resource_file(filename).to_s)
626
+ end
627
+
628
+ def self.quote_json_string(s)
629
+ Hocon::Impl::ConfigImplUtil.render_json_string(s)
630
+ end
631
+
632
+ ##################
633
+ # RSpec Tests
634
+ ##################
635
+ def self.check_equal_objects(first_object, second_object)
636
+ it "should find the two objects to be equal" do
637
+ not_equal_to_anything_else = TestUtils::NotEqualToAnythingElse.new
638
+
639
+ # Equality
640
+ expect(first_object).to eq(second_object)
641
+ expect(second_object).to eq(first_object)
642
+
643
+ # Hashes
644
+ expect(first_object.hash).to eq(second_object.hash)
645
+
646
+ # Other random object
647
+ expect(first_object).not_to eq(not_equal_to_anything_else)
648
+ expect(not_equal_to_anything_else).not_to eq(first_object)
649
+
650
+ expect(second_object).not_to eq(not_equal_to_anything_else)
651
+ expect(not_equal_to_anything_else).not_to eq(second_object)
652
+ end
653
+ end
654
+
655
+ def self.check_not_equal_objects(first_object, second_object)
656
+
657
+ it "should find the two objects to be not equal" do
658
+ not_equal_to_anything_else = TestUtils::NotEqualToAnythingElse.new
659
+
660
+ # Equality
661
+ expect(first_object).not_to eq(second_object)
662
+ expect(second_object).not_to eq(first_object)
663
+
664
+ # Hashes
665
+ # hashcode inequality isn't guaranteed, but
666
+ # as long as it happens to work it might
667
+ # detect a bug (if hashcodes are equal,
668
+ # check if it's due to a bug or correct
669
+ # before you remove this)
670
+ expect(first_object.hash).not_to eq(second_object.hash)
671
+
672
+ # Other random object
673
+ expect(first_object).not_to eq(not_equal_to_anything_else)
674
+ expect(not_equal_to_anything_else).not_to eq(first_object)
675
+
676
+ expect(second_object).not_to eq(not_equal_to_anything_else)
677
+ expect(not_equal_to_anything_else).not_to eq(second_object)
678
+ end
679
+ end
680
+ end
681
+
682
+
683
+ ##################
684
+ # RSpec Shared Examples
685
+ ##################
686
+
687
+ # Examples for comparing an object that won't equal anything but itself
688
+ # Used in the object_equality examples below
689
+ shared_examples_for "not_equal_to_other_random_thing" do
690
+ let(:not_equal_to_anything_else) { TestUtils::NotEqualToAnythingElse.new }
691
+
692
+ it "should find the first object not equal to a random other thing" do
693
+ expect(first_object).not_to eq(not_equal_to_anything_else)
694
+ expect(not_equal_to_anything_else).not_to eq(first_object)
695
+ end
696
+
697
+ it "should find the second object not equal to a random other thing" do
698
+ expect(second_object).not_to eq(not_equal_to_anything_else)
699
+ expect(not_equal_to_anything_else).not_to eq(second_object)
700
+ end
701
+ end
702
+
703
+ # Examples for making sure two objects are equal
704
+ shared_examples_for "object_equality" do
705
+
706
+ it "should find the first object to be equal to the second object" do
707
+ expect(first_object).to eq(second_object)
708
+ end
709
+
710
+ it "should find the second object to be equal to the first object" do
711
+ expect(second_object).to eq(first_object)
712
+ end
713
+
714
+ it "should find the hash codes of the two objects to be equal" do
715
+ expect(first_object.hash).to eq(second_object.hash)
716
+ end
717
+
718
+ include_examples "not_equal_to_other_random_thing"
719
+ end
720
+
721
+ # Examples for making sure two objects are not equal
722
+ shared_examples_for "object_inequality" do
723
+
724
+ it "should find the first object to not be equal to the second object" do
725
+ expect(first_object).not_to eq(second_object)
726
+ end
727
+
728
+ it "should find the second object to not be equal to the first object" do
729
+ expect(second_object).not_to eq(first_object)
730
+ end
731
+
732
+ it "should find the hash codes of the two objects to not be equal" do
733
+ # hashcode inequality isn't guaranteed, but
734
+ # as long as it happens to work it might
735
+ # detect a bug (if hashcodes are equal,
736
+ # check if it's due to a bug or correct
737
+ # before you remove this)
738
+ expect(first_object.hash).not_to eq(second_object.hash)
739
+ end
740
+
741
+ include_examples "not_equal_to_other_random_thing"
742
+ end
743
+
744
+
745
+ shared_examples_for "path_render_test" do
746
+ it "should find the expected rendered text equal to the rendered path" do
747
+ expect(path.render).to eq(expected)
748
+ end
749
+
750
+ it "should find the path equal to the parsed expected text" do
751
+ expect(Hocon::Impl::PathParser.parse_path(expected)).to eq(path)
752
+ end
753
+
754
+ it "should find the path equal to the parsed text that came from the rendered path" do
755
+ expect(Hocon::Impl::PathParser.parse_path(path.render)).to eq(path)
756
+ end
757
+ end