# vim:sw=4:ts=4 # $Id$ # require 'test/unit/testsuite' require 'test/unit/testcase' require 'yaml' class YAML_Unit_Tests < Test::Unit::TestCase # # Convert between YAML and the object to verify correct parsing and # emitting # def assert_to_yaml( obj, yaml ) assert_equal( obj, YAML::load( yaml ) ) assert_equal( obj, YAML::parse( yaml ).transform ) assert_equal( obj, YAML::load( obj.to_yaml ) ) assert_equal( obj, YAML::parse( obj.to_yaml ).transform ) assert_equal( obj, YAML::load( obj.to_yaml( :UseVersion => true, :UseHeader => true, :SortKeys => true ) ) ) end # # Test parser only # def assert_parse_only( obj, yaml ) assert_equal( obj, YAML::load( yaml ) ) assert_equal( obj, YAML::parse( yaml ).transform ) end def assert_path_segments( path, segments ) YAML::YPath.each_path( path ) { |choice| assert_equal( choice.segments, segments.shift ) } assert_equal( segments.length, 0, "Some segments leftover: #{ segments.inspect }" ) end # # Make a time with the time zone # def mktime( year, mon, day, hour, min, sec, usec, zone = "Z" ) usec = usec.to_s.to_f * 1000000 val = Time::utc( year.to_i, mon.to_i, day.to_i, hour.to_i, min.to_i, sec.to_i, usec ) if zone != "Z" hour = zone[0,3].to_i * 3600 min = zone[3,2].to_i * 60 ofs = (hour + min) val = Time.at( val.to_f - ofs ) end return val end # # Tests modified from 00basic.t in YAML.pm # def test_basic_map # Simple map assert_parse_only( { 'one' => 'foo', 'three' => 'baz', 'two' => 'bar' }, < 'simple string', 2 => 42, 3 => '1 Single Quoted String', 4 => 'YAML\'s Double "Quoted" String', 5 => "A block\n with several\n lines.\n", 6 => "A \"chomped\" block", 7 => "A folded\n string\n" }, < A folded string EOY ) end # # Test the specification examples # - Many examples have been changes because of whitespace problems that # caused the two to be inequivalent, or keys to be sorted wrong # def test_spec_simple_implicit_sequence # Simple implicit sequence assert_to_yaml( [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ], < 65, 'avg' => 0.278, 'rbi' => 147 }, < [ 'Boston Red Sox', 'Detroit Tigers', 'New York Yankees' ], 'national' => [ 'New York Mets', 'Chicago Cubs', 'Atlanta Braves' ] }, < 'Mark McGwire', 'hr' => 65, 'avg' => 0.278}, {'name' => 'Sammy Sosa', 'hr' => 63, 'avg' => 0.288} ], < { 'hr' => 65, 'avg' => 0.278 }, 'Sammy Sosa' => { 'hr' => 63, 'avg' => 0.288 } }, < [ 'Mark McGwire', 'Sammy Sosa' ], 'rbi' => [ 'Sammy Sosa', 'Ken Griffey' ] }, < [ 'Mark McGwire', 'Sammy Sosa' ], 'rbi' => [ 'Sammy Sosa', 'Ken Griffey' ] }, <"EDI", "departure"=>"LAX", "fareref"=>"DOGMA", "currency"=>"GBP"}, {"arrival"=>"MEL", "departure"=>"SYD", "fareref"=>"MADF", "currency"=>"AUD"}, {"arrival"=>"MCO", "departure"=>"JFK", "fareref"=>"DFSF", "currency"=>"USD"}], <["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"}]}, < [ Date.new( 2001, 7, 23 ) ], [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ] }, < [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ], [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ] }, < 34843, 'date' => Date.new( 2001, 1, 23 ), 'bill-to' => 'Chris Dumars', 'product' => [ { 'item' => 'Super Hoop', 'quantity' => 1 }, { 'item' => 'Basketball', 'quantity' => 4 }, { 'item' => 'Big Shoes', 'quantity' => 1 } ] }, < nil }, [ { 'five' => [ 'six' ] } ], [ 'seven' ] ], [ 'eight', 'nine' ] ], < Mark McGwire's year was crippled by a knee injury. EOY ) end def test_spec_preserve_indent # Preserve indented spaces assert_parse_only( "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n", < Sammy Sosa completed another fine season with great stats. 63 Home Runs 0.288 Batting Average What a year! EOY ) end def test_spec_indentation_determines_scope assert_parse_only( { 'name' => 'Mark McGwire', 'accomplishment' => "Mark set a major league home run record in 1998.\n", 'stats' => "65 Home Runs\n0.278 Batting Average\n" }, < Mark set a major league home run record in 1998. stats: | 65 Home Runs 0.278 Batting Average EOY ) end def test_spec_multiline_scalars # Multiline flow scalars assert_parse_only( { 'plain' => 'This unquoted scalar spans many lines.', 'quoted' => "So does this quoted scalar.\n" }, < 12345, 'decimal' => 12345, 'octal' => '014'.oct, 'hexadecimal' => '0xC'.hex }, < 1230.15, 'exponential' => 1230.15, 'fixed' => 1230.15, 'negative infinity' => -1.0/0.0 }, < nil, true => true, false => false, 'string' => '12345' }, < 'Chris', 'family' => 'Dumars', 'address' => { 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 } } assert_parse_only( { 'invoice' => 34843, 'date' => Date.new( 2001, 1, 23 ), 'bill-to' => id001, 'ship-to' => id001, 'product' => [ { 'sku' => 'BL394D', 'quantity' => 4, 'description' => 'Basketball', 'price' => 450.00 }, { 'sku' => 'BL4438H', 'quantity' => 1, 'description' => 'Super Hoop', 'price' => 2392.00 } ], 'tax' => 251.42, 'total' => 4443.52, 'comments' => "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n" }, < Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338. EOY ) end def test_spec_log_file doc_ct = 0 YAML::load_documents( < This is an error message for the log file --- Time: 2001-11-23 15:02:31 -05:00 User: ed Warning: > A slightly different error message. --- Date: 2001-11-23 15:03:17 -05:00 User: ed Fatal: > Unknown variable "bar" Stack: - file: TopClass.py line: 23 code: | x = MoreObject("345\\n") - file: MoreClass.py line: 58 code: |- foo = bar EOY ) { |doc| case doc_ct when 0 assert_equal( doc, { 'Time' => mktime( 2001, 11, 23, 15, 01, 42, 00, "-05:00" ), 'User' => 'ed', 'Warning' => "This is an error message for the log file\n" } ) when 1 assert_equal( doc, { 'Time' => mktime( 2001, 11, 23, 15, 02, 31, 00, "-05:00" ), 'User' => 'ed', 'Warning' => "A slightly different error message.\n" } ) when 2 assert_equal( doc, { 'Date' => mktime( 2001, 11, 23, 15, 03, 17, 00, "-05:00" ), 'User' => 'ed', 'Fatal' => "Unknown variable \"bar\"\n", 'Stack' => [ { 'file' => 'TopClass.py', 'line' => 23, 'code' => "x = MoreObject(\"345\\n\")\n" }, { 'file' => 'MoreClass.py', 'line' => 58, 'code' => "foo = bar" } ] } ) end doc_ct += 1 } assert_equal( doc_ct, 3 ) end def test_spec_root_fold y = YAML::load( < This YAML 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. EOY ) assert_equal( y, "This YAML 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.\n" ) end def test_spec_root_mapping y = YAML::load( < 34843, 'date' => Date.new( 2001, 1, 23 ), 'total' => 4443.52 } ) end def test_spec_oneline_docs doc_ct = 0 YAML::load_documents( < { "customers"=> [ { "given"=>"Chris", "type"=>"domain customer", "family"=>"Dumars" } ], "type"=>"domain invoice" } }, <"contains three lines of text.\nThe third one starts with a\n# character. This isn't a comment.\n"}, < 12, 'also int' => 12, 'string' => '12' }, < 8, 'color' => 'black' } ) when 1 assert_equal( doc['bearing'].type_id, 'x-private:ball' ) assert_equal( doc['bearing'].transform.value, { 'material' => 'steel' } ) end doc_ct += 1 } assert_equal( doc_ct, 2 ) end def test_spec_url_escaping YAML.add_domain_type( "domain.tld,2002", "type0" ) { |type, val| "ONE: #{val}" } YAML.add_domain_type( "domain.tld,2002", "type%30" ) { |type, val| "TWO: #{val}" } assert_parse_only( { 'same' => [ 'ONE: value', 'ONE: value' ], 'different' => [ 'TWO: value' ] }, < 'This scalar has an anchor.', 'override' => a001, 'alias' => a001 }, < The alias node below is a repeated use of this value. alias : *A001 EOY ) end def test_spec_explicit_families YAML.add_domain_type( "somewhere.com,2002", /^type$/ ) { |type, val| "SOMEWHERE: #{val}" } assert_parse_only( { '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" }, <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"]], < [], 'in-line' => [ 'one', 'two', 'three', 'four', 'five' ], 'nested' => [ 'First item in top sequence', [ 'Subordinate sequence entry' ], "A multi-line sequence entry\n", 'Sixth item in top sequence' ] }, < A multi-line sequence entry - Sixth item in top sequence EOY ) end def test_spec_builtin_map # Assortment of mappings assert_parse_only( { 'empty' => {}, 'in-line' => { 'one' => 1, 'two' => 2 }, 'spanning' => { 'one' => 1, 'two' => 2 }, 'nested' => { 'first' => 'First entry', 'second' => { 'key' => 'Subordinate mapping' }, 'third' => [ 'Subordinate sequence', {}, 'Previous mapping is empty.', { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' }, 'The previous entry is equal to the following one.', { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' } ], 12.0 => 'This key is a float.', "?\n" => 'This key had to be protected.', "\a" => 'This key had to be escaped.', "This is a multi-line folded key\n" => "Whose value is also multi-line.\n", [ 'This key', 'is a sequence' ] => [ 'With a sequence value.' ] } }, < ? : This key had to be protected. "\\a" : This key had to be escaped. ? > This is a multi-line folded key : > Whose value is also multi-line. ? - This key - is a sequence : - With a sequence value. # The following parses correctly, # but Ruby 1.6.* fails the comparison! # ? # This: key # is a: mapping # : # with a: mapping value. EOY ) end def test_spec_builtin_literal_blocks # Assortment of literal scalar blocks assert_parse_only( {"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"}, < str1, 'same as "clipped" above' => str1, 'stripped' => str2, 'same as "stripped" above' => str2, 'kept' => str3, 'same as "kept" above' => str3 }, <"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"}, <"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"}, < mktime( 2001, 12, 14, 21, 59, 43, ".10", "-05:00" ), "canonical" => mktime( 2001, 12, 15, 2, 59, 43, ".10" ), "date (noon UTC)" => Date.new( 2002, 12, 14), "valid iso8601" => mktime( 2001, 12, 14, 21, 59, 43, ".10", "-05:00" ) }, < arrow_gif, 'base64' => arrow_gif, 'description' => "The binary value above is a tiny arrow encoded as a gif image.\n" }, < /George McFly/i }, < 2, :UseVersion => 0 ) y.add( { 'hi' => 'hello', 'map' => { 'good' => 'two' }, 'time' => Time.now, 'try' => /^po(.*)$/, 'bye' => 'goodbye' } ) y.add( { 'po' => 'nil', 'oper' => 90 } ) y.add( { 'hi' => 'wow!', 'bye' => 'wow!' } ) y.add( { [ 'Red Socks', 'Boston' ] => [ 'One', 'Two', 'Three' ] } ) y.add( [ true, false, false ] ) end # # Test YPath choices parsing # def test_ypath_parsing assert_path_segments( "/*/((one|three)/name|place)|//place", [ ["*", "one", "name"], ["*", "three", "name"], ["*", "place"], ["/", "place"] ] ) end # # Tests from Tanaka Akira on [ruby-core] # def test_akira # Commas in plain scalars [ruby-core:1066] assert_to_yaml( {"A"=>"A,","B"=>"B"}, <2, "2"=>3}, <"b"}] * 2, <"b", "c"=>"d"} } # YAML::load( a.to_yaml ) end # # Circular references # def test_circular_references a = []; a[0] = a; a[1] = a inspect_str = "[[...], [...]]" assert_equal( inspect_str, YAML::load( a.to_yaml ).inspect ) end end if $0 == __FILE__ suite = Test::Unit::TestSuite.new('YAML') ObjectSpace.each_object(Class) do |klass| suite << klass.suite if (Test::Unit::TestCase > klass) end require 'test/unit/ui/console/testrunner' Test::Unit::UI::Console::TestRunner.run(suite).passed? end