diff --git a/ChangeLog b/ChangeLog index 65ab4dd1b4..35779411dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +Sat Oct 4 04:48:49 2003 why the lucky stiff + + * ext/syck/rubyext.c: default keys handled. + + * ext/syck/syck.h: lowered default buffer size to 16k for increased + performance. + + * test/yaml: checkin of basic unit tests. + Sat Oct 4 04:24:19 2003 GOTOU Yuuzou * ext/openssl/extconf.rb: add check for X509V3_set_nconf. diff --git a/ext/syck/rubyext.c b/ext/syck/rubyext.c index 5b350474f6..e7ec534a8f 100644 --- a/ext/syck/rubyext.c +++ b/ext/syck/rubyext.c @@ -40,10 +40,10 @@ typedef struct RVALUE { /* * symbols and constants */ -static ID s_new, s_utc, s_at, s_to_f, s_read, s_binmode, s_call, s_transfer, s_update, s_dup, s_match, s_keys, s_to_str, s_unpack, s_tr_bang; +static ID s_new, s_utc, s_at, s_to_f, s_read, s_binmode, s_call, s_transfer, s_update, s_dup, s_match, s_keys, s_to_str, s_unpack, s_tr_bang, s_anchors, s_default_set; static VALUE sym_model, sym_generic; static VALUE sym_scalar, sym_seq, sym_map; -VALUE cDate, cParser, cLoader, cNode, cPrivateType, cDomainType, cBadAlias, cMergeKey, cEmitter; +VALUE cDate, cParser, cLoader, cNode, cPrivateType, cDomainType, cBadAlias, cDefaultKey, cMergeKey, cEmitter; VALUE oDefaultLoader; /* @@ -364,9 +364,6 @@ yaml_org_handler( n, ref ) long i = 0; VALUE obj = Qnil; - /* - * If prefixed with YAML_DOMAIN, skip to type name - */ switch (n->kind) { case syck_str_kind: @@ -464,6 +461,10 @@ yaml_org_handler( n, ref ) { obj = rb_funcall( cMergeKey, s_new, 0 ); } + else if ( strncmp( type_id, "default", 7 ) == 0 ) + { + obj = rb_funcall( cDefaultKey, s_new, 0 ); + } else { transferred = 0; @@ -489,7 +490,7 @@ yaml_org_handler( n, ref ) { VALUE k = syck_map_read( n, map_key, i ); VALUE v = syck_map_read( n, map_value, i ); - int merge_key = 0; + int skip_aset = 0; /* * Handle merge keys @@ -501,7 +502,7 @@ yaml_org_handler( n, ref ) VALUE dup = rb_funcall( v, s_dup, 0 ); rb_funcall( dup, s_update, 1, obj ); obj = dup; - merge_key = 1; + skip_aset = 1; } else if ( rb_obj_is_kind_of( v, rb_cArray ) ) { @@ -513,12 +514,17 @@ yaml_org_handler( n, ref ) rb_ary_push( v, obj ); rb_iterate( rb_each, v, syck_merge_i, dup ); obj = dup; - merge_key = 1; + skip_aset = 1; } } } + else if ( rb_obj_is_kind_of( k, cDefaultKey ) ) + { + rb_funcall( obj, s_default_set, 1, v ); + skip_aset = 1; + } - if ( ! merge_key ) + if ( ! skip_aset ) { rb_hash_aset( obj, k, v ); } @@ -762,6 +768,7 @@ syck_loader_initialize( self ) rb_iv_set(self, "@families", rb_hash_new() ); rb_iv_set(self, "@private_types", rb_hash_new() ); + rb_iv_set(self, "@anchors", rb_hash_new() ); families = rb_iv_get(self, "@families"); rb_hash_aset(families, rb_str_new2( YAML_DOMAIN ), rb_hash_new()); @@ -1274,11 +1281,13 @@ Init_syck() s_at = rb_intern("at"); s_to_f = rb_intern("to_f"); s_read = rb_intern("read"); + s_anchors = rb_intern("anchors"); s_binmode = rb_intern("binmode"); s_transfer = rb_intern("transfer"); s_call = rb_intern("call"); s_update = rb_intern("update"); s_dup = rb_intern("dup"); + s_default_set = rb_intern("default="); s_match = rb_intern("match"); s_keys = rb_intern("keys"); s_to_str = rb_intern("to_str"); @@ -1303,6 +1312,7 @@ Init_syck() cLoader = rb_define_class_under( rb_syck, "Loader", rb_cObject ); rb_define_attr( cLoader, "families", 1, 1 ); rb_define_attr( cLoader, "private_types", 1, 1 ); + rb_define_attr( cLoader, "anchors", 1, 1 ); rb_define_method( cLoader, "initialize", syck_loader_initialize, 0 ); rb_define_method( cLoader, "add_domain_type", syck_loader_add_domain_type, -1 ); rb_define_method( cLoader, "add_builtin_type", syck_loader_add_builtin_type, -1 ); @@ -1364,6 +1374,11 @@ Init_syck() */ cMergeKey = rb_define_class_under( rb_syck, "MergeKey", rb_cObject ); + /* + * Define YAML::Syck::DefaultKey class + */ + cDefaultKey = rb_define_class_under( rb_syck, "DefaultKey", rb_cObject ); + /* * Define YAML::Syck::Emitter class */ diff --git a/ext/syck/syck.h b/ext/syck/syck.h index 49efd35da0..8c10f0d27e 100644 --- a/ext/syck/syck.h +++ b/ext/syck/syck.h @@ -47,7 +47,7 @@ extern "C" { #endif #define ALLOC_CT 8 -#define SYCK_BUFFERSIZE 262144 +#define SYCK_BUFFERSIZE 16384 #define S_ALLOC_N(type,n) (type*)malloc(sizeof(type)*(n)) #define S_ALLOC(type) (type*)malloc(sizeof(type)) #define S_REALLOC_N(var,type,n) (var)=(type*)realloc((char*)(var),sizeof(type)*(n)) diff --git a/test/yaml/test_yaml.rb b/test/yaml/test_yaml.rb new file mode 100644 index 0000000000..8baad11e5f --- /dev/null +++ b/test/yaml/test_yaml.rb @@ -0,0 +1,1155 @@ +# 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