1# -*- coding: us-ascii; mode: ruby; ruby-indent-level: 4; tab-width: 4 -*-
2#												vim:sw=4:ts=4
3# $Id$
4#
5require 'psych/helper'
6require 'ostruct'
7
8# [ruby-core:01946]
9module Psych_Tests
10    StructTest = Struct::new( :c )
11end
12
13class Psych_Unit_Tests < Psych::TestCase
14    def teardown
15        Psych.domain_types.clear
16    end
17
18    def test_y_method
19      assert_raises(NoMethodError) do
20        OpenStruct.new.y 1
21      end
22    end
23
24    def test_syck_compat
25      time = Time.utc(2010, 10, 10)
26      yaml = Psych.dump time
27      assert_match "2010-10-10 00:00:00.000000000 Z", yaml
28    end
29
30    # [ruby-core:34969]
31    def test_regexp_with_n
32        assert_cycle(Regexp.new('',0,'n'))
33    end
34	#
35	# Tests modified from 00basic.t in Psych.pm
36	#
37	def test_basic_map
38		# Simple map
39		assert_parse_only(
40			{ 'one' => 'foo', 'three' => 'baz', 'two' => 'bar' }, <<EOY
41one: foo
42two: bar
43three: baz
44EOY
45		)
46	end
47
48	def test_basic_strings
49		# Common string types
50		assert_cycle("x")
51		assert_cycle(":x")
52		assert_cycle(":")
53		assert_parse_only(
54			{ 1 => 'simple string', 2 => 42, 3 => '1 Single Quoted String',
55			  4 => 'Psych\'s Double "Quoted" String', 5 => "A block\n  with several\n    lines.\n",
56			  6 => "A \"chomped\" block", 7 => "A folded\n string\n", 8 => ": started string" },
57			  <<EOY
581: simple string
592: 42
603: '1 Single Quoted String'
614: "Psych's Double \\\"Quoted\\\" String"
625: |
63  A block
64    with several
65      lines.
666: |-
67  A "chomped" block
687: >
69  A
70  folded
71   string
728: ": started string"
73EOY
74		)
75	end
76
77	#
78	# Test the specification examples
79	# - Many examples have been changes because of whitespace problems that
80	#   caused the two to be inequivalent, or keys to be sorted wrong
81	#
82
83	def test_spec_simple_implicit_sequence
84	  	# Simple implicit sequence
85		assert_to_yaml(
86			[ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ], <<EOY
87- Mark McGwire
88- Sammy Sosa
89- Ken Griffey
90EOY
91		)
92	end
93
94	def test_spec_simple_implicit_map
95		# Simple implicit map
96		assert_to_yaml(
97			{ 'hr' => 65, 'avg' => 0.278, 'rbi' => 147 }, <<EOY
98avg: 0.278
99hr: 65
100rbi: 147
101EOY
102		)
103	end
104
105	def test_spec_simple_map_with_nested_sequences
106		# Simple mapping with nested sequences
107		assert_to_yaml(
108			{ 'american' =>
109			  [ 'Boston Red Sox', 'Detroit Tigers', 'New York Yankees' ],
110			  'national' =>
111			  [ 'New York Mets', 'Chicago Cubs', 'Atlanta Braves' ] }, <<EOY
112american:
113  - Boston Red Sox
114  - Detroit Tigers
115  - New York Yankees
116national:
117  - New York Mets
118  - Chicago Cubs
119  - Atlanta Braves
120EOY
121		)
122	end
123
124	def test_spec_simple_sequence_with_nested_map
125		# Simple sequence with nested map
126		assert_to_yaml(
127		  [
128		    {'name' => 'Mark McGwire', 'hr' => 65, 'avg' => 0.278},
129			{'name' => 'Sammy Sosa', 'hr' => 63, 'avg' => 0.288}
130		  ], <<EOY
131-
132  avg: 0.278
133  hr: 65
134  name: Mark McGwire
135-
136  avg: 0.288
137  hr: 63
138  name: Sammy Sosa
139EOY
140		)
141	end
142
143	def test_spec_sequence_of_sequences
144		# Simple sequence with inline sequences
145		assert_parse_only(
146		  [
147		  	[ 'name', 'hr', 'avg' ],
148			[ 'Mark McGwire', 65, 0.278 ],
149			[ 'Sammy Sosa', 63, 0.288 ]
150		  ], <<EOY
151- [ name         , hr , avg   ]
152- [ Mark McGwire , 65 , 0.278 ]
153- [ Sammy Sosa   , 63 , 0.288 ]
154EOY
155		)
156	end
157
158	def test_spec_mapping_of_mappings
159		# Simple map with inline maps
160		assert_parse_only(
161		  { 'Mark McGwire' =>
162		    { 'hr' => 65, 'avg' => 0.278 },
163			'Sammy Sosa' =>
164		    { 'hr' => 63, 'avg' => 0.288 }
165		  }, <<EOY
166Mark McGwire: {hr: 65, avg: 0.278}
167Sammy Sosa:   {hr: 63,
168               avg: 0.288}
169EOY
170		)
171	end
172
173    def test_ambiguous_comments
174        # [ruby-talk:88012]
175        assert_to_yaml( "Call the method #dave", <<EOY )
176--- "Call the method #dave"
177EOY
178    end
179
180	def test_spec_nested_comments
181		# Map and sequences with comments
182		assert_parse_only(
183		  { 'hr' => [ 'Mark McGwire', 'Sammy Sosa' ],
184		    'rbi' => [ 'Sammy Sosa', 'Ken Griffey' ] }, <<EOY
185hr: # 1998 hr ranking
186  - Mark McGwire
187  - Sammy Sosa
188rbi:
189  # 1998 rbi ranking
190  - Sammy Sosa
191  - Ken Griffey
192EOY
193		)
194	end
195
196	def test_spec_anchors_and_aliases
197		# Anchors and aliases
198		assert_parse_only(
199			{ 'hr' =>
200			  [ 'Mark McGwire', 'Sammy Sosa' ],
201			  'rbi' =>
202			  [ 'Sammy Sosa', 'Ken Griffey' ] }, <<EOY
203hr:
204   - Mark McGwire
205   # Name "Sammy Sosa" scalar SS
206   - &SS Sammy Sosa
207rbi:
208   # So it can be referenced later.
209   - *SS
210   - Ken Griffey
211EOY
212	 	)
213
214        assert_to_yaml(
215            [{"arrival"=>"EDI", "departure"=>"LAX", "fareref"=>"DOGMA", "currency"=>"GBP"}, {"arrival"=>"MEL", "departure"=>"SYD", "fareref"=>"MADF", "currency"=>"AUD"}, {"arrival"=>"MCO", "departure"=>"JFK", "fareref"=>"DFSF", "currency"=>"USD"}], <<EOY
216  -
217    &F fareref: DOGMA
218    &C currency: GBP
219    &D departure: LAX
220    &A arrival: EDI
221  - { *F: MADF, *C: AUD, *D: SYD, *A: MEL }
222  - { *F: DFSF, *C: USD, *D: JFK, *A: MCO }
223EOY
224        )
225
226        assert_to_yaml(
227            {"ALIASES"=>["fareref", "currency", "departure", "arrival"], "FARES"=>[{"arrival"=>"EDI", "departure"=>"LAX", "fareref"=>"DOGMA", "currency"=>"GBP"}, {"arrival"=>"MEL", "departure"=>"SYD", "fareref"=>"MADF", "currency"=>"AUD"}, {"arrival"=>"MCO", "departure"=>"JFK", "fareref"=>"DFSF", "currency"=>"USD"}]}, <<EOY
228---
229ALIASES: [&f fareref, &c currency, &d departure, &a arrival]
230FARES:
231- *f: DOGMA
232  *c: GBP
233  *d: LAX
234  *a: EDI
235
236- *f: MADF
237  *c: AUD
238  *d: SYD
239  *a: MEL
240
241- *f: DFSF
242  *c: USD
243  *d: JFK
244  *a: MCO
245
246EOY
247        )
248
249	end
250
251	def test_spec_mapping_between_sequences
252		# Complex key #1
253		dj = Date.new( 2001, 7, 23 )
254		assert_parse_only(
255			{ [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ],
256			  [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ] }, <<EOY
257? # PLAY SCHEDULE
258  - Detroit Tigers
259  - Chicago Cubs
260:
261  - 2001-07-23
262
263? [ New York Yankees,
264    Atlanta Braves ]
265: [ 2001-07-02, 2001-08-12,
266    2001-08-14 ]
267EOY
268		)
269
270		# Complex key #2
271		assert_parse_only(
272		  { [ 'New York Yankees', 'Atlanta Braves' ] =>
273		    [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ),
274			  Date.new( 2001, 8, 14 ) ],
275			[ 'Detroit Tigers', 'Chicago Cubs' ] =>
276			[ Date.new( 2001, 7, 23 ) ]
277		  }, <<EOY
278?
279    - New York Yankees
280    - Atlanta Braves
281:
282  - 2001-07-02
283  - 2001-08-12
284  - 2001-08-14
285?
286    - Detroit Tigers
287    - Chicago Cubs
288:
289  - 2001-07-23
290EOY
291		)
292	end
293
294	def test_spec_sequence_key_shortcut
295		# Shortcut sequence map
296		assert_parse_only(
297		  { 'invoice' => 34843, 'date' => Date.new( 2001, 1, 23 ),
298		    'bill-to' => 'Chris Dumars', 'product' =>
299			[ { 'item' => 'Super Hoop', 'quantity' => 1 },
300			  { 'item' => 'Basketball', 'quantity' => 4 },
301			  { 'item' => 'Big Shoes', 'quantity' => 1 } ] }, <<EOY
302invoice: 34843
303date   : 2001-01-23
304bill-to: Chris Dumars
305product:
306  - item    : Super Hoop
307    quantity: 1
308  - item    : Basketball
309    quantity: 4
310  - item    : Big Shoes
311    quantity: 1
312EOY
313		)
314	end
315
316    def test_spec_sequence_in_sequence_shortcut
317        # Seq-in-seq
318        assert_parse_only( [ [ [ 'one', 'two', 'three' ] ] ], <<EOY )
319- - - one
320    - two
321    - three
322EOY
323    end
324
325    def test_spec_sequence_shortcuts
326        # Sequence shortcuts combined
327        assert_parse_only(
328[
329  [
330    [ [ 'one' ] ],
331    [ 'two', 'three' ],
332    { 'four' => nil },
333    [ { 'five' => [ 'six' ] } ],
334    [ 'seven' ]
335  ],
336  [ 'eight', 'nine' ]
337], <<EOY )
338- - - - one
339  - - two
340    - three
341  - four:
342  - - five:
343      - six
344  - - seven
345- - eight
346  - nine
347EOY
348    end
349
350	def test_spec_single_literal
351		# Literal scalar block
352		assert_parse_only( [ "\\/|\\/|\n/ |  |_\n" ], <<EOY )
353- |
354    \\/|\\/|
355    / |  |_
356EOY
357	end
358
359	def test_spec_single_folded
360		# Folded scalar block
361		assert_parse_only(
362			[ "Mark McGwire's year was crippled by a knee injury.\n" ], <<EOY
363- >
364    Mark McGwire\'s
365    year was crippled
366    by a knee injury.
367EOY
368		)
369	end
370
371	def test_spec_preserve_indent
372		# Preserve indented spaces
373		assert_parse_only(
374			"Sammy Sosa completed another fine season with great stats.\n\n  63 Home Runs\n  0.288 Batting Average\n\nWhat a year!\n", <<EOY
375--- >
376 Sammy Sosa completed another
377 fine season with great stats.
378
379   63 Home Runs
380   0.288 Batting Average
381
382 What a year!
383EOY
384		)
385	end
386
387	def test_spec_indentation_determines_scope
388		assert_parse_only(
389			{ 'name' => 'Mark McGwire', 'accomplishment' => "Mark set a major league home run record in 1998.\n",
390			  'stats' => "65 Home Runs\n0.278 Batting Average\n" }, <<EOY
391name: Mark McGwire
392accomplishment: >
393   Mark set a major league
394   home run record in 1998.
395stats: |
396   65 Home Runs
397   0.278 Batting Average
398EOY
399		)
400	end
401
402	def test_spec_multiline_scalars
403		# Multiline flow scalars
404	 	assert_parse_only(
405	 		{ 'plain' => 'This unquoted scalar spans many lines.',
406	 		  'quoted' => "So does this quoted scalar.\n" }, <<EOY
407plain: This unquoted
408       scalar spans
409       many lines.
410quoted: "\\
411  So does this quoted
412  scalar.\\n"
413EOY
414		)
415	end
416
417	def test_spec_type_int
418		assert_parse_only(
419			{ 'canonical' => 12345, 'decimal' => 12345, 'octal' => '014'.oct, 'hexadecimal' => '0xC'.hex }, <<EOY
420canonical: 12345
421decimal: +12,345
422octal: 014
423hexadecimal: 0xC
424EOY
425		)
426		assert_parse_only(
427            { 'canonical' => 685230, 'decimal' => 685230, 'octal' => 02472256, 'hexadecimal' => 0x0A74AE, 'sexagesimal' => 685230 }, <<EOY)
428canonical: 685230
429decimal: +685,230
430octal: 02472256
431hexadecimal: 0x0A,74,AE
432sexagesimal: 190:20:30
433EOY
434	end
435
436	def test_spec_type_float
437		assert_parse_only(
438			{ 'canonical' => 1230.15, 'exponential' => 1230.15, 'fixed' => 1230.15,
439			  'negative infinity' => -1.0/0.0 }, <<EOY)
440canonical: 1.23015e+3
441exponential: 12.3015e+02
442fixed: 1,230.15
443negative infinity: -.inf
444EOY
445		nan = Psych::load( <<EOY )
446not a number: .NaN
447EOY
448		assert( nan['not a number'].nan? )
449	end
450
451	def test_spec_type_misc
452		assert_parse_only(
453			{ nil => nil, true => true, false => false, 'string' => '12345' }, <<EOY
454null: ~
455true: yes
456false: no
457string: '12345'
458EOY
459		)
460	end
461
462	def test_spec_complex_invoice
463		# Complex invoice type
464		id001 = { 'given' => 'Chris', 'family' => 'Dumars', 'address' =>
465			{ 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak',
466			  'state' => 'MI', 'postal' => 48046 } }
467		assert_parse_only(
468			{ 'invoice' => 34843, 'date' => Date.new( 2001, 1, 23 ),
469			  'bill-to' => id001, 'ship-to' => id001, 'product' =>
470			  [ { 'sku' => 'BL394D', 'quantity' => 4,
471			      'description' => 'Basketball', 'price' => 450.00 },
472				{ 'sku' => 'BL4438H', 'quantity' => 1,
473				  'description' => 'Super Hoop', 'price' => 2392.00 } ],
474			  'tax' => 251.42, 'total' => 4443.52,
475			  'comments' => "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n" }, <<EOY
476invoice: 34843
477date   : 2001-01-23
478bill-to: &id001
479    given  : Chris
480    family : !str Dumars
481    address:
482        lines: |
483            458 Walkman Dr.
484            Suite #292
485        city    : Royal Oak
486        state   : MI
487        postal  : 48046
488ship-to: *id001
489product:
490    - !map
491      sku         : BL394D
492      quantity    : 4
493      description : Basketball
494      price       : 450.00
495    - sku         : BL4438H
496      quantity    : 1
497      description : Super Hoop
498      price       : 2392.00
499tax  : 251.42
500total: 4443.52
501comments: >
502    Late afternoon is best.
503    Backup contact is Nancy
504    Billsmer @ 338-4338.
505EOY
506		)
507	end
508
509	def test_spec_log_file
510		doc_ct = 0
511		Psych::load_documents( <<EOY
512---
513Time: 2001-11-23 15:01:42 -05:00
514User: ed
515Warning: >
516  This is an error message
517  for the log file
518---
519Time: 2001-11-23 15:02:31 -05:00
520User: ed
521Warning: >
522  A slightly different error
523  message.
524---
525Date: 2001-11-23 15:03:17 -05:00
526User: ed
527Fatal: >
528  Unknown variable "bar"
529Stack:
530  - file: TopClass.py
531    line: 23
532    code: |
533      x = MoreObject("345\\n")
534  - file: MoreClass.py
535    line: 58
536    code: |-
537      foo = bar
538EOY
539		) { |doc|
540			case doc_ct
541				when 0
542					assert_equal( doc, { 'Time' => mktime( 2001, 11, 23, 15, 01, 42, 00, "-05:00" ),
543						'User' => 'ed', 'Warning' => "This is an error message for the log file\n" } )
544				when 1
545					assert_equal( doc, { 'Time' => mktime( 2001, 11, 23, 15, 02, 31, 00, "-05:00" ),
546						'User' => 'ed', 'Warning' => "A slightly different error message.\n" } )
547				when 2
548					assert_equal( doc, { 'Date' => mktime( 2001, 11, 23, 15, 03, 17, 00, "-05:00" ),
549						'User' => 'ed', 'Fatal' => "Unknown variable \"bar\"\n",
550						'Stack' => [
551							{ 'file' => 'TopClass.py', 'line' => 23, 'code' => "x = MoreObject(\"345\\n\")\n" },
552							{ 'file' => 'MoreClass.py', 'line' => 58, 'code' => "foo = bar" } ] } )
553			end
554			doc_ct += 1
555		}
556		assert_equal( doc_ct, 3 )
557	end
558
559	def test_spec_root_fold
560		y = Psych::load( <<EOY
561---
562This Psych stream contains a single text value.
563The next stream is a log file - a sequence of
564log entries. Adding an entry to the log is a
565simple matter of appending it at the end.
566EOY
567		)
568		assert_equal( y, "This Psych stream contains a single text value. The next stream is a log file - a sequence of log entries. Adding an entry to the log is a simple matter of appending it at the end." )
569	end
570
571	def test_spec_root_mapping
572		y = Psych::load( <<EOY
573# This stream is an example of a top-level mapping.
574invoice : 34843
575date    : 2001-01-23
576total   : 4443.52
577EOY
578		)
579		assert_equal( y, { 'invoice' => 34843, 'date' => Date.new( 2001, 1, 23 ), 'total' => 4443.52 } )
580	end
581
582	def test_spec_oneline_docs
583		doc_ct = 0
584		Psych::load_documents( <<EOY
585# The following is a sequence of three documents.
586# The first contains an empty mapping, the second
587# an empty sequence, and the last an empty string.
588--- {}
589--- [ ]
590--- ''
591EOY
592		) { |doc|
593			case doc_ct
594				when 0
595					assert_equal( doc, {} )
596				when 1
597					assert_equal( doc, [] )
598				when 2
599					assert_equal( doc, '' )
600			end
601			doc_ct += 1
602		}
603		assert_equal( doc_ct, 3 )
604	end
605
606	def test_spec_domain_prefix
607        customer_proc = proc { |type, val|
608            if Hash === val
609                scheme, domain, type = type.split( ':', 3 )
610                val['type'] = "domain #{type}"
611                val
612            else
613                raise ArgumentError, "Not a Hash in domain.tld,2002/invoice: " + val.inspect
614            end
615        }
616        Psych.add_domain_type( "domain.tld,2002", 'invoice', &customer_proc )
617        Psych.add_domain_type( "domain.tld,2002", 'customer', &customer_proc )
618		assert_parse_only( { "invoice"=> { "customers"=> [ { "given"=>"Chris", "type"=>"domain customer", "family"=>"Dumars" } ], "type"=>"domain invoice" } }, <<EOY
619# 'http://domain.tld,2002/invoice' is some type family.
620invoice: !domain.tld,2002/invoice
621  # 'seq' is shorthand for 'http://yaml.org/seq'.
622  # This does not effect '^customer' below
623  # because it is does not specify a prefix.
624  customers: !seq
625    # '^customer' is shorthand for the full
626    # notation 'http://domain.tld,2002/customer'.
627    - !customer
628      given : Chris
629      family : Dumars
630EOY
631		)
632	end
633
634	def test_spec_throwaway
635		assert_parse_only(
636			{"this"=>"contains three lines of text.\nThe third one starts with a\n# character. This isn't a comment.\n"}, <<EOY
637### These are four throwaway comment  ###
638
639### lines (the second line is empty). ###
640this: |   # Comments may trail lines.
641    contains three lines of text.
642    The third one starts with a
643    # character. This isn't a comment.
644
645# These are three throwaway comment
646# lines (the first line is empty).
647EOY
648		)
649	end
650
651	def test_spec_force_implicit
652		# Force implicit
653		assert_parse_only(
654			{ 'integer' => 12, 'also int' => 12, 'string' => '12' }, <<EOY
655integer: 12
656also int: ! "12"
657string: !str 12
658EOY
659		)
660	end
661
662    ###
663    # Commenting out this test.  This line:
664    #
665    #   - !domain.tld,2002/type\\x30 value
666    #
667    # Is invalid according to the YAML spec:
668    #
669    #   http://yaml.org/spec/1.1/#id896876
670    #
671#	def test_spec_url_escaping
672#		Psych.add_domain_type( "domain.tld,2002", "type0" ) { |type, val|
673#			"ONE: #{val}"
674#		}
675#		Psych.add_domain_type( "domain.tld,2002", "type%30" ) { |type, val|
676#			"TWO: #{val}"
677#		}
678#		assert_parse_only(
679#			{ 'same' => [ 'ONE: value', 'ONE: value' ], 'different' => [ 'TWO: value' ] }, <<EOY
680#same:
681#  - !domain.tld,2002/type\\x30 value
682#  - !domain.tld,2002/type0 value
683#different: # As far as the Psych parser is concerned
684#  - !domain.tld,2002/type%30 value
685#EOY
686#		)
687#	end
688
689	def test_spec_override_anchor
690		# Override anchor
691		a001 = "The alias node below is a repeated use of this value.\n"
692		assert_parse_only(
693			{ 'anchor' => 'This scalar has an anchor.', 'override' => a001, 'alias' => a001 }, <<EOY
694anchor : &A001 This scalar has an anchor.
695override : &A001 >
696 The alias node below is a
697 repeated use of this value.
698alias : *A001
699EOY
700		)
701	end
702
703	def test_spec_explicit_families
704        Psych.add_domain_type( "somewhere.com,2002", 'type' ) { |type, val|
705            "SOMEWHERE: #{val}"
706        }
707		assert_parse_only(
708			{ 'not-date' => '2002-04-28', 'picture' => "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236i^\020' \202\n\001\000;", 'hmm' => "SOMEWHERE: family above is short for\nhttp://somewhere.com/type\n" }, <<EOY
709not-date: !str 2002-04-28
710picture: !binary |
711 R0lGODlhDAAMAIQAAP//9/X
712 17unp5WZmZgAAAOfn515eXv
713 Pz7Y6OjuDg4J+fn5OTk6enp
714 56enmleECcgggoBADs=
715
716hmm: !somewhere.com,2002/type |
717 family above is short for
718 http://somewhere.com/type
719EOY
720		)
721	end
722
723	def test_spec_application_family
724		# Testing the clarkevans.com graphs
725		Psych.add_domain_type( "clarkevans.com,2002", 'graph/shape' ) { |type, val|
726			if Array === val
727				val << "Shape Container"
728				val
729			else
730				raise ArgumentError, "Invalid graph of type #{val.class}: " + val.inspect
731			end
732		}
733		one_shape_proc = Proc.new { |type, val|
734			if Hash === val
735                type = type.split( /:/ )
736				val['TYPE'] = "Shape: #{type[2]}"
737				val
738			else
739				raise ArgumentError, "Invalid graph of type #{val.class}: " + val.inspect
740			end
741		}
742		Psych.add_domain_type( "clarkevans.com,2002", 'graph/circle', &one_shape_proc )
743		Psych.add_domain_type( "clarkevans.com,2002", 'graph/line', &one_shape_proc )
744		Psych.add_domain_type( "clarkevans.com,2002", 'graph/text', &one_shape_proc )
745        # MODIFIED to remove invalid Psych
746		assert_parse_only(
747			[[{"radius"=>7, "center"=>{"x"=>73, "y"=>129}, "TYPE"=>"Shape: graph/circle"}, {"finish"=>{"x"=>89, "y"=>102}, "TYPE"=>"Shape: graph/line", "start"=>{"x"=>73, "y"=>129}}, {"TYPE"=>"Shape: graph/text", "value"=>"Pretty vector drawing.", "start"=>{"x"=>73, "y"=>129}, "color"=>16772795}, "Shape Container"]], <<EOY
748- !clarkevans.com,2002/graph/shape
749  - !/graph/circle
750    center: &ORIGIN {x: 73, y: 129}
751    radius: 7
752  - !/graph/line # !clarkevans.com,2002/graph/line
753    start: *ORIGIN
754    finish: { x: 89, y: 102 }
755  - !/graph/text
756    start: *ORIGIN
757    color: 0xFFEEBB
758    value: Pretty vector drawing.
759EOY
760		)
761	end
762
763	def test_spec_float_explicit
764		assert_parse_only(
765			[ 10.0, 10.0, 10.0, 10.0 ], <<EOY
766# All entries in the sequence
767# have the same type and value.
768- 10.0
769- !float 10
770- !yaml.org,2002/float '10'
771- !yaml.org,2002/float "\\
772  1\\
773  0"
774EOY
775		)
776	end
777
778	def test_spec_builtin_seq
779		# Assortment of sequences
780		assert_parse_only(
781			{ 'empty' => [], 'in-line' => [ 'one', 'two', 'three', 'four', 'five' ],
782			  'nested' => [ 'First item in top sequence', [ 'Subordinate sequence entry' ],
783			  	"A multi-line sequence entry\n", 'Sixth item in top sequence' ] }, <<EOY
784empty: []
785in-line: [ one, two, three # May span lines,
786         , four,           # indentation is
787           five ]          # mostly ignored.
788nested:
789 - First item in top sequence
790 -
791  - Subordinate sequence entry
792 - >
793   A multi-line
794   sequence entry
795 - Sixth item in top sequence
796EOY
797		)
798	end
799
800	def test_spec_builtin_map
801		# Assortment of mappings
802		assert_parse_only(
803			{ 'empty' => {}, 'in-line' => { 'one' => 1, 'two' => 2 },
804			  'spanning' => { 'one' => 1, 'two' => 2 },
805			  'nested' => { 'first' => 'First entry', 'second' =>
806			  	{ 'key' => 'Subordinate mapping' }, 'third' =>
807				  [ 'Subordinate sequence', {}, 'Previous mapping is empty.',
808					{ 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' },
809					'The previous entry is equal to the following one.',
810					{ 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' } ],
811				  12.0 => 'This key is a float.', "?\n" => 'This key had to be protected.',
812				  "\a" => 'This key had to be escaped.',
813				  "This is a multi-line folded key\n" => "Whose value is also multi-line.\n",
814				  [ 'This key', 'is a sequence' ] => [ 'With a sequence value.' ] } }, <<EOY
815
816empty: {}
817in-line: { one: 1, two: 2 }
818spanning: { one: 1,
819   two: 2 }
820nested:
821 first : First entry
822 second:
823  key: Subordinate mapping
824 third:
825  - Subordinate sequence
826  - { }
827  - Previous mapping is empty.
828  - A key: value pair in a sequence.
829    A second: key:value pair.
830  - The previous entry is equal to the following one.
831  -
832    A key: value pair in a sequence.
833    A second: key:value pair.
834 !float 12 : This key is a float.
835 ? >
836  ?
837 : This key had to be protected.
838 "\\a" : This key had to be escaped.
839 ? >
840   This is a
841   multi-line
842   folded key
843 : >
844   Whose value is
845   also multi-line.
846 ?
847  - This key
848  - is a sequence
849 :
850  - With a sequence value.
851# The following parses correctly,
852# but Ruby 1.6.* fails the comparison!
853# ?
854#  This: key
855#  is a: mapping
856# :
857#  with a: mapping value.
858EOY
859		)
860	end
861
862	def test_spec_builtin_literal_blocks
863		# Assortment of literal scalar blocks
864		assert_parse_only(
865			{"both are equal to"=>"  This has no newline.", "is equal to"=>"The \\ ' \" characters may be\nfreely used. Leading white\n   space is significant.\n\nLine breaks are significant.\nThus this value contains one\nempty line and ends with a\nsingle line break, but does\nnot start with one.\n", "also written as"=>"  This has no newline.", "indented and chomped"=>"  This has no newline.", "empty"=>"", "literal"=>"The \\ ' \" characters may be\nfreely used. Leading white\n   space is significant.\n\nLine breaks are significant.\nThus this value contains one\nempty line and ends with a\nsingle line break, but does\nnot start with one.\n"}, <<EOY
866empty: |
867
868literal: |
869 The \\\ ' " characters may be
870 freely used. Leading white
871    space is significant.
872
873 Line breaks are significant.
874 Thus this value contains one
875 empty line and ends with a
876 single line break, but does
877 not start with one.
878
879is equal to: "The \\\\ ' \\" characters may \\
880 be\\nfreely used. Leading white\\n   space \\
881 is significant.\\n\\nLine breaks are \\
882 significant.\\nThus this value contains \\
883 one\\nempty line and ends with a\\nsingle \\
884 line break, but does\\nnot start with one.\\n"
885
886# Comments may follow a nested
887# scalar value. They must be
888# less indented.
889
890# Modifiers may be combined in any order.
891indented and chomped: |2-
892    This has no newline.
893
894also written as: |-2
895    This has no newline.
896
897both are equal to: "  This has no newline."
898EOY
899		)
900
901		str1 = "This has one newline.\n"
902		str2 = "This has no newline."
903		str3 = "This has two newlines.\n\n"
904		assert_parse_only(
905			{ 'clipped' => str1, 'same as "clipped" above' => str1,
906			  'stripped' => str2, 'same as "stripped" above' => str2,
907			  'kept' => str3, 'same as "kept" above' => str3 }, <<EOY
908clipped: |
909    This has one newline.
910
911same as "clipped" above: "This has one newline.\\n"
912
913stripped: |-
914    This has no newline.
915
916same as "stripped" above: "This has no newline."
917
918kept: |+
919    This has two newlines.
920
921same as "kept" above: "This has two newlines.\\n\\n"
922
923EOY
924		)
925	end
926
927	def test_spec_span_single_quote
928		assert_parse_only( {"third"=>"a single quote ' must be escaped.", "second"=>"! : \\ etc. can be used freely.", "is same as"=>"this contains six spaces\nand one line break", "empty"=>"", "span"=>"this contains six spaces\nand one line break"}, <<EOY
929empty: ''
930second: '! : \\ etc. can be used freely.'
931third: 'a single quote '' must be escaped.'
932span: 'this contains
933      six spaces
934
935      and one
936      line break'
937is same as: "this contains six spaces\\nand one line break"
938EOY
939		)
940	end
941
942	def test_spec_span_double_quote
943		assert_parse_only( {"is equal to"=>"this contains four  spaces", "third"=>"a \" or a \\ must be escaped.", "second"=>"! : etc. can be used freely.", "empty"=>"", "fourth"=>"this value ends with an LF.\n", "span"=>"this contains four  spaces"}, <<EOY
944empty: ""
945second: "! : etc. can be used freely."
946third: "a \\\" or a \\\\ must be escaped."
947fourth: "this value ends with an LF.\\n"
948span: "this contains
949  four  \\
950      spaces"
951is equal to: "this contains four  spaces"
952EOY
953		)
954	end
955
956	def test_spec_builtin_time
957		# Time
958		assert_parse_only(
959			{ "space separated" => mktime( 2001, 12, 14, 21, 59, 43, ".10", "-05:00" ),
960			  "canonical" => mktime( 2001, 12, 15, 2, 59, 43, ".10" ),
961			  "date (noon UTC)" => Date.new( 2002, 12, 14),
962			  "valid iso8601" => mktime( 2001, 12, 14, 21, 59, 43, ".10", "-05:00" ) }, <<EOY
963canonical: 2001-12-15T02:59:43.1Z
964valid iso8601: 2001-12-14t21:59:43.10-05:00
965space separated: 2001-12-14 21:59:43.10 -05:00
966date (noon UTC): 2002-12-14
967EOY
968		)
969	end
970
971	def test_spec_builtin_binary
972		arrow_gif = "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236iiiccc\243\243\243\204\204\204\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371!\376\016Made with GIMP\000,\000\000\000\000\f\000\f\000\000\005,  \216\2010\236\343@\024\350i\020\304\321\212\010\034\317\200M$z\357\3770\205p\270\2601f\r\e\316\001\303\001\036\020' \202\n\001\000;"
973		assert_parse_only(
974			{ 'canonical' => arrow_gif, 'base64' => arrow_gif,
975			  'description' => "The binary value above is a tiny arrow encoded as a gif image.\n" }, <<EOY
976canonical: !binary "\\
977 R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOf\\
978 n515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmlpaW\\
979 NjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++\\
980 f/++f/++f/++f/++f/++f/++f/++SH+Dk1hZGUg\\
981 d2l0aCBHSU1QACwAAAAADAAMAAAFLCAgjoEwnuN\\
982 AFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84Bww\\
983 EeECcgggoBADs="
984base64: !binary |
985 R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOf
986 n515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmlpaW
987 NjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++
988 f/++f/++f/++f/++f/++f/++f/++SH+Dk1hZGUg
989 d2l0aCBHSU1QACwAAAAADAAMAAAFLCAgjoEwnuN
990 AFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84Bww
991 EeECcgggoBADs=
992description: >
993 The binary value above is a tiny arrow
994 encoded as a gif image.
995EOY
996		)
997	end
998	def test_ruby_regexp
999		# Test Ruby regular expressions
1000		assert_to_yaml(
1001			{ 'simple' => /a.b/, 'complex' => %r'\A"((?:[^"]|\")+)"',
1002			  'case-insensitive' => /George McFly/i }, <<EOY
1003case-insensitive: !ruby/regexp "/George McFly/i"
1004complex: !ruby/regexp "/\\\\A\\"((?:[^\\"]|\\\\\\")+)\\"/"
1005simple: !ruby/regexp "/a.b/"
1006EOY
1007		)
1008	end
1009
1010    #
1011    # Test of Ranges
1012    #
1013    def test_ranges
1014
1015        # Simple numeric
1016        assert_to_yaml( 1..3, <<EOY )
1017--- !ruby/range 1..3
1018EOY
1019
1020        # Simple alphabetic
1021        assert_to_yaml( 'a'..'z', <<EOY )
1022--- !ruby/range a..z
1023EOY
1024
1025        # Float
1026        assert_to_yaml( 10.5...30.3, <<EOY )
1027--- !ruby/range 10.5...30.3
1028EOY
1029
1030    end
1031
1032	def test_ruby_struct
1033		# Ruby structures
1034		book_struct = Struct::new( "MyBookStruct", :author, :title, :year, :isbn )
1035		assert_to_yaml(
1036			[ book_struct.new( "Yukihiro Matsumoto", "Ruby in a Nutshell", 2002, "0-596-00214-9" ),
1037			  book_struct.new( [ 'Dave Thomas', 'Andy Hunt' ], "The Pickaxe", 2002,
1038				book_struct.new( "This should be the ISBN", "but I have another struct here", 2002, "None" )
1039			  ) ], <<EOY
1040- !ruby/struct:MyBookStruct
1041  author: Yukihiro Matsumoto
1042  title: Ruby in a Nutshell
1043  year: 2002
1044  isbn: 0-596-00214-9
1045- !ruby/struct:MyBookStruct
1046  author:
1047    - Dave Thomas
1048    - Andy Hunt
1049  title: The Pickaxe
1050  year: 2002
1051  isbn: !ruby/struct:MyBookStruct
1052    author: This should be the ISBN
1053    title: but I have another struct here
1054    year: 2002
1055    isbn: None
1056EOY
1057		)
1058
1059        assert_to_yaml( Psych_Tests::StructTest.new( 123 ), <<EOY )
1060--- !ruby/struct:Psych_Tests::StructTest
1061c: 123
1062EOY
1063
1064	end
1065
1066	def test_ruby_rational
1067		assert_to_yaml( Rational(1, 2), <<EOY )
1068--- !ruby/object:Rational
1069numerator: 1
1070denominator: 2
1071EOY
1072
1073		# Read Psych dumped by the ruby 1.8.3.
1074		assert_to_yaml( Rational(1, 2), "!ruby/object:Rational 1/2\n" )
1075		assert_raises( ArgumentError ) { Psych.load("!ruby/object:Rational INVALID/RATIONAL\n") }
1076	end
1077
1078	def test_ruby_complex
1079		assert_to_yaml( Complex(3, 4), <<EOY )
1080--- !ruby/object:Complex
1081image: 4
1082real: 3
1083EOY
1084
1085		# Read Psych dumped by the ruby 1.8.3.
1086		assert_to_yaml( Complex(3, 4), "!ruby/object:Complex 3+4i\n" )
1087		assert_raises( ArgumentError ) { Psych.load("!ruby/object:Complex INVALID+COMPLEXi\n") }
1088	end
1089
1090	def test_emitting_indicators
1091		assert_to_yaml( "Hi, from Object 1. You passed: please, pretty please", <<EOY
1092--- "Hi, from Object 1. You passed: please, pretty please"
1093EOY
1094		)
1095	end
1096
1097	##
1098	## Test the Psych::Stream class -- INACTIVE at the moment
1099	##
1100	#def test_document
1101	#	y = Psych::Stream.new( :Indent => 2, :UseVersion => 0 )
1102	#	y.add(
1103	#		{ 'hi' => 'hello', 'map' =>
1104	#			{ 'good' => 'two' },
1105	#		  'time' => Time.now,
1106	#		  'try' => /^po(.*)$/,
1107	#		  'bye' => 'goodbye'
1108	#		}
1109	#	)
1110	#	y.add( { 'po' => 'nil', 'oper' => 90 } )
1111	#	y.add( { 'hi' => 'wow!', 'bye' => 'wow!' } )
1112	#	y.add( { [ 'Red Socks', 'Boston' ] => [ 'One', 'Two', 'Three' ] } )
1113	#	y.add( [ true, false, false ] )
1114	#end
1115
1116    #
1117    # Test YPath choices parsing
1118    #
1119    #def test_ypath_parsing
1120    #    assert_path_segments( "/*/((one|three)/name|place)|//place",
1121    #      [ ["*", "one", "name"],
1122    #        ["*", "three", "name"],
1123    #        ["*", "place"],
1124    #        ["/", "place"] ]
1125    #    )
1126    #end
1127
1128    #
1129    # Tests from Tanaka Akira on [ruby-core]
1130    #
1131    def test_akira
1132
1133        # Commas in plain scalars [ruby-core:1066]
1134        assert_to_yaml(
1135            {"A"=>"A,","B"=>"B"}, <<EOY
1136A: "A,"
1137B: B
1138EOY
1139        )
1140
1141        # Double-quoted keys [ruby-core:1069]
1142        assert_to_yaml(
1143            {"1"=>2, "2"=>3}, <<EOY
1144'1': 2
1145"2": 3
1146EOY
1147        )
1148
1149        # Anchored mapping [ruby-core:1071]
1150        assert_to_yaml(
1151            [{"a"=>"b"}] * 2, <<EOY
1152- &id001
1153  a: b
1154- *id001
1155EOY
1156        )
1157
1158        # Stress test [ruby-core:1071]
1159        # a = []; 1000.times { a << {"a"=>"b", "c"=>"d"} }
1160        # Psych::load( a.to_yaml )
1161
1162    end
1163
1164    #
1165    # Test Time.now cycle
1166    #
1167    def test_time_now_cycle
1168        #
1169        # From Minero Aoki [ruby-core:2305]
1170        #
1171        #require 'yaml'
1172        t = Time.now
1173        t = Time.at(t.tv_sec, t.tv_usec)
1174        5.times do
1175            assert_cycle(t)
1176        end
1177    end
1178
1179    #
1180    # Test Range cycle
1181    #
1182    def test_range_cycle
1183      #
1184      # From Minero Aoki [ruby-core:02306]
1185      #
1186      assert_cycle("a".."z")
1187
1188      #
1189      # From Nobu Nakada [ruby-core:02311]
1190      #
1191      assert_cycle(0..1)
1192      assert_cycle(1.0e20 .. 2.0e20)
1193      assert_cycle("0".."1")
1194      assert_cycle(".."..."...")
1195      assert_cycle(".rb"..".pl")
1196      assert_cycle(".rb"...".pl")
1197      assert_cycle('"'...".")
1198      assert_cycle("'"...".")
1199    end
1200
1201    #
1202    # Circular references
1203    #
1204    def test_circular_references
1205        a = []; a[0] = a; a[1] = a
1206        inspect_str = "[[...], [...]]"
1207        assert_equal( inspect_str, Psych::load(Psych.dump(a)).inspect )
1208    end
1209
1210    #
1211    # Test Symbol cycle
1212    #
1213    def test_symbol_cycle
1214      #
1215      # From Aaron Schrab [ruby-Bugs:2535]
1216      #
1217      assert_cycle(:"^foo")
1218    end
1219
1220    #
1221    # Test Numeric cycle
1222    #
1223    class NumericTest < Numeric
1224      def initialize(value)
1225        @value = value
1226      end
1227      def ==(other)
1228        @value == other.instance_eval{ @value }
1229      end
1230    end
1231    def test_numeric_cycle
1232      assert_cycle(1) # Fixnum
1233      assert_cycle(111111111111111111111111111111111) # Bignum
1234      assert_cycle(NumericTest.new(3)) # Subclass of Numeric
1235    end
1236
1237    #
1238    # Test empty map/seq in map cycle
1239    #
1240    def test_empty_map_key
1241      #
1242      # empty seq as key
1243      #
1244      assert_cycle({[]=>""})
1245
1246      #
1247      # empty map as key
1248      #
1249      assert_cycle({{}=>""})
1250    end
1251
1252    #
1253    # contributed by riley lynch [ruby-Bugs-8548]
1254    #
1255    def test_object_id_collision
1256      omap = Psych::Omap.new
1257      1000.times { |i| omap["key_#{i}"] = { "value" => i } }
1258      raise "id collision in ordered map" if Psych.dump(omap) =~ /id\d+/
1259    end
1260
1261    def test_date_out_of_range
1262      Psych::load('1900-01-01T00:00:00+00:00')
1263    end
1264
1265    def test_normal_exit
1266      Psych.load("2000-01-01 00:00:00.#{"0"*1000} +00:00\n")
1267      # '[ruby-core:13735]'
1268    end
1269
1270    def test_multiline_string_uses_literal_style
1271      yaml = Psych.dump("multi\nline\nstring")
1272      assert_match("|", yaml)
1273    end
1274
1275    def test_string_starting_with_non_word_character_uses_double_quotes_without_exclamation_mark
1276      yaml = Psych.dump("@123'abc")
1277      refute_match("!", yaml)
1278    end
1279
1280    def test_string_dump_with_colon
1281      yaml = Psych.dump 'x: foo'
1282      refute_match '!', yaml
1283    end
1284
1285    def test_string_dump_starting_with_star
1286      yaml = Psych.dump '*foo'
1287      refute_match '!', yaml
1288    end
1289end
1290