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