1# coding: utf-8 2 3require 'psych/helper' 4 5module Psych 6 class TestParser < TestCase 7 class EventCatcher < Handler 8 attr_accessor :parser 9 attr_reader :calls, :marks 10 def initialize 11 @parser = nil 12 @calls = [] 13 @marks = [] 14 end 15 16 (Handler.instance_methods(true) - 17 Object.instance_methods).each do |m| 18 class_eval %{ 19 def #{m} *args 20 super 21 @marks << @parser.mark if @parser 22 @calls << [:#{m}, args] 23 end 24 } 25 end 26 end 27 28 def setup 29 super 30 @handler = EventCatcher.new 31 @parser = Psych::Parser.new @handler 32 @handler.parser = @parser 33 end 34 35 def test_ast_roundtrip 36 parser = Psych.parser 37 parser.parse('null') 38 ast = parser.handler.root 39 assert_match(/^null/, ast.yaml) 40 end 41 42 def test_exception_memory_leak 43 yaml = <<-eoyaml 44%YAML 1.1 45%TAG ! tag:tenderlovemaking.com,2009: 46--- &ponies 47- first element 48- *ponies 49- foo: bar 50... 51 eoyaml 52 53 [:start_stream, :start_document, :end_document, :alias, :scalar, 54 :start_sequence, :end_sequence, :start_mapping, :end_mapping, 55 :end_stream].each do |method| 56 57 klass = Class.new(Psych::Handler) do 58 define_method(method) do |*args| 59 raise 60 end 61 end 62 63 parser = Psych::Parser.new klass.new 64 2.times { 65 assert_raises(RuntimeError, method.to_s) do 66 parser.parse yaml 67 end 68 } 69 end 70 end 71 72 def test_multiparse 73 3.times do 74 @parser.parse '--- foo' 75 end 76 end 77 78 def test_filename 79 ex = assert_raises(Psych::SyntaxError) do 80 @parser.parse '--- `', 'omg!' 81 end 82 assert_match 'omg!', ex.message 83 end 84 85 def test_line_numbers 86 assert_equal 0, @parser.mark.line 87 @parser.parse "---\n- hello\n- world" 88 line_calls = @handler.marks.map(&:line).zip(@handler.calls.map(&:first)) 89 assert_equal [[0, :start_stream], 90 [0, :start_document], 91 [1, :start_sequence], 92 [2, :scalar], 93 [3, :scalar], 94 [3, :end_sequence], 95 [3, :end_document], 96 [3, :end_stream]], line_calls 97 98 assert_equal 3, @parser.mark.line 99 end 100 101 def test_column_numbers 102 assert_equal 0, @parser.mark.column 103 @parser.parse "---\n- hello\n- world" 104 col_calls = @handler.marks.map(&:column).zip(@handler.calls.map(&:first)) 105 assert_equal [[0, :start_stream], 106 [3, :start_document], 107 [1, :start_sequence], 108 [0, :scalar], 109 [0, :scalar], 110 [0, :end_sequence], 111 [0, :end_document], 112 [0, :end_stream]], col_calls 113 114 assert_equal 0, @parser.mark.column 115 end 116 117 def test_index_numbers 118 assert_equal 0, @parser.mark.index 119 @parser.parse "---\n- hello\n- world" 120 idx_calls = @handler.marks.map(&:index).zip(@handler.calls.map(&:first)) 121 assert_equal [[0, :start_stream], 122 [3, :start_document], 123 [5, :start_sequence], 124 [12, :scalar], 125 [19, :scalar], 126 [19, :end_sequence], 127 [19, :end_document], 128 [19, :end_stream]], idx_calls 129 130 assert_equal 19, @parser.mark.index 131 end 132 133 def test_bom 134 tadpole = '���������������������' 135 136 # BOM + text 137 yml = "\uFEFF#{tadpole}".encode('UTF-16LE') 138 @parser.parse yml 139 assert_equal tadpole, @parser.handler.calls[2][1].first 140 end 141 142 def test_external_encoding 143 tadpole = '���������������������' 144 145 @parser.external_encoding = Psych::Parser::UTF16LE 146 @parser.parse tadpole.encode 'UTF-16LE' 147 assert_equal tadpole, @parser.handler.calls[2][1].first 148 end 149 150 def test_bogus_io 151 o = Object.new 152 def o.external_encoding; nil end 153 def o.read len; self end 154 155 assert_raises(TypeError) do 156 @parser.parse o 157 end 158 end 159 160 def test_parse_io 161 @parser.parse StringIO.new("--- a") 162 assert_called :start_stream 163 assert_called :scalar 164 assert_called :end_stream 165 end 166 167 def test_syntax_error 168 assert_raises(Psych::SyntaxError) do 169 @parser.parse("---\n\"foo\"\n\"bar\"\n") 170 end 171 end 172 173 def test_syntax_error_twice 174 assert_raises(Psych::SyntaxError) do 175 @parser.parse("---\n\"foo\"\n\"bar\"\n") 176 end 177 178 assert_raises(Psych::SyntaxError) do 179 @parser.parse("---\n\"foo\"\n\"bar\"\n") 180 end 181 end 182 183 def test_syntax_error_has_path_for_string 184 e = assert_raises(Psych::SyntaxError) do 185 @parser.parse("---\n\"foo\"\n\"bar\"\n") 186 end 187 assert_match '(<unknown>):', e.message 188 end 189 190 def test_syntax_error_has_path_for_io 191 io = StringIO.new "---\n\"foo\"\n\"bar\"\n" 192 def io.path; "hello!"; end 193 194 e = assert_raises(Psych::SyntaxError) do 195 @parser.parse(io) 196 end 197 assert_match "(#{io.path}):", e.message 198 end 199 200 def test_mapping_end 201 @parser.parse("---\n!!map { key: value }") 202 assert_called :end_mapping 203 end 204 205 def test_mapping_tag 206 @parser.parse("---\n!!map { key: value }") 207 assert_called :start_mapping, ["tag:yaml.org,2002:map", false, Nodes::Mapping::FLOW] 208 end 209 210 def test_mapping_anchor 211 @parser.parse("---\n&A { key: value }") 212 assert_called :start_mapping, ['A', true, Nodes::Mapping::FLOW] 213 end 214 215 def test_mapping_block 216 @parser.parse("---\n key: value") 217 assert_called :start_mapping, [true, Nodes::Mapping::BLOCK] 218 end 219 220 def test_mapping_start 221 @parser.parse("---\n{ key: value }") 222 assert_called :start_mapping 223 assert_called :start_mapping, [true, Nodes::Mapping::FLOW] 224 end 225 226 def test_sequence_end 227 @parser.parse("---\n&A [1, 2]") 228 assert_called :end_sequence 229 end 230 231 def test_sequence_start_anchor 232 @parser.parse("---\n&A [1, 2]") 233 assert_called :start_sequence, ["A", true, Nodes::Sequence::FLOW] 234 end 235 236 def test_sequence_start_tag 237 @parser.parse("---\n!!seq [1, 2]") 238 assert_called :start_sequence, ["tag:yaml.org,2002:seq", false, Nodes::Sequence::FLOW] 239 end 240 241 def test_sequence_start_flow 242 @parser.parse("---\n[1, 2]") 243 assert_called :start_sequence, [true, Nodes::Sequence::FLOW] 244 end 245 246 def test_sequence_start_block 247 @parser.parse("---\n - 1\n - 2") 248 assert_called :start_sequence, [true, Nodes::Sequence::BLOCK] 249 end 250 251 def test_literal_scalar 252 @parser.parse(<<-eoyml) 253%YAML 1.1 254--- 255"literal\n\ 256 \ttext\n" 257 eoyml 258 assert_called :scalar, ['literal text ', false, true, Nodes::Scalar::DOUBLE_QUOTED] 259 end 260 261 def test_scalar 262 @parser.parse("--- foo\n") 263 assert_called :scalar, ['foo', true, false, Nodes::Scalar::PLAIN] 264 end 265 266 def test_scalar_with_tag 267 @parser.parse("---\n!!str foo\n") 268 assert_called :scalar, ['foo', 'tag:yaml.org,2002:str', false, false, Nodes::Scalar::PLAIN] 269 end 270 271 def test_scalar_with_anchor 272 @parser.parse("---\n&A foo\n") 273 assert_called :scalar, ['foo', 'A', true, false, Nodes::Scalar::PLAIN] 274 end 275 276 def test_scalar_plain_implicit 277 @parser.parse("---\n&A foo\n") 278 assert_called :scalar, ['foo', 'A', true, false, Nodes::Scalar::PLAIN] 279 end 280 281 def test_alias 282 @parser.parse(<<-eoyml) 283%YAML 1.1 284--- 285!!seq [ 286 !!str "Without properties", 287 &A !!str "Anchored", 288 !!str "Tagged", 289 *A, 290 !!str "", 291] 292 eoyml 293 assert_called :alias, ['A'] 294 end 295 296 def test_end_stream 297 @parser.parse("--- foo\n") 298 assert_called :end_stream 299 end 300 301 def test_start_stream 302 @parser.parse("--- foo\n") 303 assert_called :start_stream 304 end 305 306 def test_end_document_implicit 307 @parser.parse("\"foo\"\n") 308 assert_called :end_document, [true] 309 end 310 311 def test_end_document_explicit 312 @parser.parse("\"foo\"\n...") 313 assert_called :end_document, [false] 314 end 315 316 def test_start_document_version 317 @parser.parse("%YAML 1.1\n---\n\"foo\"\n") 318 assert_called :start_document, [[1,1], [], false] 319 end 320 321 def test_start_document_tag 322 @parser.parse("%TAG !yaml! tag:yaml.org,2002\n---\n!yaml!str \"foo\"\n") 323 assert_called :start_document, [[], [['!yaml!', 'tag:yaml.org,2002']], false] 324 end 325 326 def assert_called call, with = nil, parser = @parser 327 if with 328 call = parser.handler.calls.find { |x| 329 x.first == call && x.last.compact == with 330 } 331 assert(call, 332 "#{[call,with].inspect} not in #{parser.handler.calls.inspect}" 333 ) 334 else 335 assert parser.handler.calls.any? { |x| x.first == call } 336 end 337 end 338 end 339end 340