From 9def3d2e30ba76433d764b63f89054b34852d2f9 Mon Sep 17 00:00:00 2001 From: Emily Date: Tue, 18 Oct 2016 17:17:45 +0200 Subject: [PATCH] RUBY-1152 Implement BSON corpus tests --- spec/bson/corpus_spec.rb | 50 ++++ spec/spec_helper.rb | 1 + spec/support/corpus-tests/array.json | 43 +++ spec/support/corpus-tests/boolean.json | 27 ++ spec/support/corpus-tests/code.json | 67 +++++ spec/support/corpus-tests/code_w_scope.json | 78 ++++++ spec/support/corpus-tests/document.json | 36 +++ spec/support/corpus-tests/double.json | 69 +++++ .../support/corpus-tests/failures/binary.json | 69 +++++ .../corpus-tests/failures/datetime.json | 31 +++ .../corpus-tests/failures/dbpointer.json | 42 +++ spec/support/corpus-tests/failures/int64.json | 38 +++ spec/support/corpus-tests/failures/regex.json | 37 +++ .../support/corpus-tests/failures/symbol.json | 62 +++++ .../corpus-tests/failures/undefined.json | 13 + spec/support/corpus-tests/int32.json | 38 +++ spec/support/corpus-tests/maxkey.json | 12 + spec/support/corpus-tests/minkey.json | 12 + spec/support/corpus-tests/null.json | 12 + spec/support/corpus-tests/oid.json | 28 ++ spec/support/corpus-tests/string.json | 67 +++++ spec/support/corpus-tests/timestamp.json | 18 ++ spec/support/corpus-tests/top.json | 62 +++++ spec/support/corpus.rb | 249 ++++++++++++++++++ 24 files changed, 1161 insertions(+) create mode 100644 spec/bson/corpus_spec.rb create mode 100644 spec/support/corpus-tests/array.json create mode 100644 spec/support/corpus-tests/boolean.json create mode 100644 spec/support/corpus-tests/code.json create mode 100644 spec/support/corpus-tests/code_w_scope.json create mode 100644 spec/support/corpus-tests/document.json create mode 100644 spec/support/corpus-tests/double.json create mode 100644 spec/support/corpus-tests/failures/binary.json create mode 100644 spec/support/corpus-tests/failures/datetime.json create mode 100644 spec/support/corpus-tests/failures/dbpointer.json create mode 100644 spec/support/corpus-tests/failures/int64.json create mode 100644 spec/support/corpus-tests/failures/regex.json create mode 100644 spec/support/corpus-tests/failures/symbol.json create mode 100644 spec/support/corpus-tests/failures/undefined.json create mode 100644 spec/support/corpus-tests/int32.json create mode 100644 spec/support/corpus-tests/maxkey.json create mode 100644 spec/support/corpus-tests/minkey.json create mode 100644 spec/support/corpus-tests/null.json create mode 100644 spec/support/corpus-tests/oid.json create mode 100644 spec/support/corpus-tests/string.json create mode 100644 spec/support/corpus-tests/timestamp.json create mode 100644 spec/support/corpus-tests/top.json create mode 100644 spec/support/corpus.rb diff --git a/spec/bson/corpus_spec.rb b/spec/bson/corpus_spec.rb new file mode 100644 index 000000000..3b732bf90 --- /dev/null +++ b/spec/bson/corpus_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe 'Driver BSON Corpus spec tests' do + + specs = BSON_CORPUS_TESTS.map { |file| BSON::Corpus::Spec.new(file) } + + specs.each do |spec| + + context(spec.description) do + + spec.valid_tests.each do |test| + + context(test.description) do + + it 'roundtrips the bson correctly' do + expect(test.reencoded_bson).to eq(test.correct_bson) + end + + context 'when the bson provided results from an incorrect encoder', if: test.test_canonical_bson? do + + it 'encodes the canonical bson correctly' do + expect(test.reencoded_canonical_bson).to eq(test.correct_bson) + end + end + + context 'when the document can be represented as extended json', if: test.test_extjson? do + + it 'decodes from bson, then encodes the document as extended json correctly' do + expect(test.extjson_from_bson).to eq(test.correct_extjson) + expect(test.extjson_from_bson[test.test_key]).to eq(test.correct_extjson[test.test_key]) + end + + it 'decodes from extended json, then encodes the document as extended json correctly' do + expect(test.extjson_from_encoded_extjson).to eq(test.correct_extjson) + expect(test.extjson_from_encoded_extjson[test.test_key]).to eq(test.correct_extjson[test.test_key]) + end + + context 'when the canonical bson can be represented as extended json', if: (test.test_canonical_bson? && test.test_extjson?) do + + it 'encodes the canonical bson correctly as extended json' do + expect(test.extjson_from_canonical_bson).to eq(test.correct_extjson) + expect(test.extjson_from_canonical_bson[test.test_key]).to eq(test.correct_extjson[test.test_key]) + end + end + end + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b9624dbe7..1c1b7d55b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,7 @@ CURRENT_PATH = File.expand_path(File.dirname(__FILE__)) DRIVER_COMMON_BSON_TESTS = Dir.glob("#{CURRENT_PATH}/support/driver-spec-tests/**/*.json") +BSON_CORPUS_TESTS = Dir.glob("#{CURRENT_PATH}/support/corpus-tests/*.json") $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) diff --git a/spec/support/corpus-tests/array.json b/spec/support/corpus-tests/array.json new file mode 100644 index 000000000..216a69d14 --- /dev/null +++ b/spec/support/corpus-tests/array.json @@ -0,0 +1,43 @@ +{ + "description": "Array", + "bson_type": "0x04", + "test_key": "a", + "valid": [ + { + "description": "Empty", + "bson": "0D000000046100050000000000", + "extjson": "{\"a\" : []}" + }, + { + "description": "Single Element Array", + "bson": "140000000461000C0000001030000A0000000000", + "extjson": "{\"a\" : [10]}" + }, + { + "description": "Single Element Array with index set incorrectly", + "bson": "130000000461000B00000010000A0000000000", + "canonical_bson": "140000000461000C0000001030000A0000000000", + "extjson": "{\"a\" : [10]}" + }, + { + "description": "Single Element Array with index set incorrectly", + "bson": "150000000461000D000000106162000A0000000000", + "canonical_bson": "140000000461000C0000001030000A0000000000", + "extjson": "{\"a\" : [10]}" + } + ], + "decodeErrors": [ + { + "description": "Array length too long: eats outer terminator", + "bson": "140000000461000D0000001030000A0000000000" + }, + { + "description": "Array length too short: leaks terminator", + "bson": "140000000461000B0000001030000A0000000000" + }, + { + "description": "Invalid Array: bad string length in field", + "bson": "1A00000004666F6F00100000000230000500000062617A000000" + } + ] +} diff --git a/spec/support/corpus-tests/boolean.json b/spec/support/corpus-tests/boolean.json new file mode 100644 index 000000000..757fa22f2 --- /dev/null +++ b/spec/support/corpus-tests/boolean.json @@ -0,0 +1,27 @@ +{ + "description": "Boolean", + "bson_type": "0x08", + "test_key": "b", + "valid": [ + { + "description": "True", + "bson": "090000000862000100", + "extjson": "{\"b\" : true}" + }, + { + "description": "False", + "bson": "090000000862000000", + "extjson": "{\"b\" : false}" + } + ], + "decodeErrors": [ + { + "description": "Invalid boolean value of 2", + "bson": "090000000862000200" + }, + { + "description": "Invalid boolean value of -1", + "bson": "09000000086200FF00" + } + ] +} diff --git a/spec/support/corpus-tests/code.json b/spec/support/corpus-tests/code.json new file mode 100644 index 000000000..58250e478 --- /dev/null +++ b/spec/support/corpus-tests/code.json @@ -0,0 +1,67 @@ +{ + "description": "Javascript Code", + "bson_type": "0x0D", + "test_key": "a", + "valid": [ + { + "description": "Empty string", + "bson": "0D0000000D6100010000000000", + "extjson": "{\"a\" : {\"$code\" : \"\"}}" + }, + { + "description": "Single character", + "bson": "0E0000000D610002000000620000", + "extjson": "{\"a\" : {\"$code\" : \"b\"}}" + }, + { + "description": "Multi-character", + "bson": "190000000D61000D0000006162616261626162616261620000", + "extjson": "{\"a\" : {\"$code\" : \"abababababab\"}}" + }, + { + "description": "two-byte UTF-8 (\u00e9)", + "bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", + "extjson": "{\"a\" : \"\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\"}" + }, + { + "description": "three-byte UTF-8 (\u2606)", + "bson": "190000000261000D000000E29886E29886E29886E298860000", + "extjson": "{\"a\" : \"\\u2606\\u2606\\u2606\\u2606\"}" + }, + { + "description": "Embedded nulls", + "bson": "190000000261000D0000006162006261620062616261620000", + "extjson": "{\"a\" : \"ab\\u0000bab\\u0000babab\"}" + } + ], + "decodeErrors": [ + { + "description": "bad code string length: 0 (but no 0x00 either)", + "bson": "0C0000000261000000000000" + }, + { + "description": "bad code string length: -1", + "bson": "0C000000026100FFFFFFFF00" + }, + { + "description": "bad code string length: eats terminator", + "bson": "10000000026100050000006200620000" + }, + { + "description": "bad code string length: longer than rest of document", + "bson": "120000000200FFFFFF00666F6F6261720000" + }, + { + "description": "code string is not null-terminated", + "bson": "1000000002610004000000616263FF00" + }, + { + "description": "empty code string, but extra null", + "bson": "0E00000002610001000000000000" + }, + { + "description": "invalid UTF-8", + "bson": "0E00000002610002000000E90000" + } + ] +} diff --git a/spec/support/corpus-tests/code_w_scope.json b/spec/support/corpus-tests/code_w_scope.json new file mode 100644 index 000000000..caae79627 --- /dev/null +++ b/spec/support/corpus-tests/code_w_scope.json @@ -0,0 +1,78 @@ +{ + "description": "Javascript Code with Scope", + "bson_type": "0x0F", + "test_key": "a", + "valid": [ + { + "description": "Empty code string, empty scope", + "bson": "160000000F61000E0000000100000000050000000000", + "extjson": "{\"a\" : {\"$code\" : \"\", \"$scope\" : {}}}" + }, + { + "description": "Non-empty code string, empty scope", + "bson": "1A0000000F610012000000050000006162636400050000000000", + "extjson": "{\"a\" : {\"$code\" : \"abcd\", \"$scope\" : {}}}" + }, + { + "description": "Empty code string, non-empty scope", + "bson": "1D0000000F61001500000001000000000C000000107800010000000000", + "extjson": "{\"a\" : {\"$code\" : \"\", \"$scope\" : {\"x\" : 1}}}" + }, + { + "description": "Non-empty code string and non-empty scope", + "bson": "210000000F6100190000000500000061626364000C000000107800010000000000", + "extjson": "{\"a\" : {\"$code\" : \"abcd\", \"$scope\" : {\"x\" : 1}}}" + }, + { + "description": "Unicode and embedded null in code string, empty scope", + "bson": "1A0000000F61001200000005000000C3A9006400050000000000", + "extjson": "{\"a\" : {\"$code\" : \"\\u00e9\\u0000d\", \"$scope\" : {}}}" + } + ], + "decodeErrors": [ + { + "description": "field length zero", + "bson": "280000000F6100000000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length negative", + "bson": "280000000F6100FFFFFFFF0500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length too short (less than minimum size)", + "bson": "160000000F61000D0000000100000000050000000000" + }, + { + "description": "field length too short (truncates scope)", + "bson": "280000000F61001F0000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length too long (clips outer doc)", + "bson": "280000000F6100210000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length too long (longer than outer doc)", + "bson": "280000000F6100FF0000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: length too short", + "bson": "280000000F6100200000000400000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: length too long (clips scope)", + "bson": "280000000F6100200000000600000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: negative length", + "bson": "280000000F610020000000FFFFFFFF61626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: length longer than field", + "bson": "280000000F610020000000FF00000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad scope doc (field has bad string length)", + "bson": "1C0000000F001500000001000000000C000000020000000000000000" + } + ] +} diff --git a/spec/support/corpus-tests/document.json b/spec/support/corpus-tests/document.json new file mode 100644 index 000000000..fdedd29f1 --- /dev/null +++ b/spec/support/corpus-tests/document.json @@ -0,0 +1,36 @@ +{ + "description": "Document type (sub-documents)", + "bson_type": "0x03", + "test_key": "x", + "valid": [ + { + "description": "Empty subdoc", + "bson": "0D000000037800050000000000", + "extjson": "{\"x\" : {}}" + }, + { + "description": "Empty-string key subdoc", + "bson": "150000000378000D00000002000200000062000000", + "extjson": "{\"x\" : {\"\" : \"b\"}}" + }, + { + "description": "Single-character key subdoc", + "bson": "160000000378000E0000000261000200000062000000", + "extjson": "{\"x\" : {\"a\" : \"b\"}}" + } + ], + "decodeErrors": [ + { + "description": "Subdocument length too long: eats outer terminator", + "bson": "1800000003666F6F000F0000001062617200FFFFFF7F0000" + }, + { + "description": "Subdocument length too short: leaks terminator", + "bson": "1500000003666F6F000A0000000862617200010000" + }, + { + "description": "Invalid subdocument: bad string length in field", + "bson": "1C00000003666F6F001200000002626172000500000062617A000000" + } + ] +} diff --git a/spec/support/corpus-tests/double.json b/spec/support/corpus-tests/double.json new file mode 100644 index 000000000..5dc1f0697 --- /dev/null +++ b/spec/support/corpus-tests/double.json @@ -0,0 +1,69 @@ +{ + "description": "Double type", + "bson_type": "0x01", + "test_key": "d", + "valid": [ + { + "description": "+1.0", + "bson": "10000000016400000000000000F03F00", + "extjson": "{\"d\" : 1.0}" + }, + { + "description": "-1.0", + "bson": "10000000016400000000000000F0BF00", + "extjson": "{\"d\" : -1.0}" + }, + { + "description": "+1.0001220703125", + "bson": "10000000016400000000008000F03F00", + "extjson": "{\"d\" : 1.0001220703125}" + }, + { + "description": "-1.0001220703125", + "bson": "10000000016400000000008000F0BF00", + "extjson": "{\"d\" : -1.0001220703125}" + }, + { + "description": "+2.0001220703125e10", + "bson": "1000000001640000807ca1a9a0124200", + "extjson": "{\"d\" : 2.0001220703125e10}" + }, + { + "description": "-2.0001220703125e10", + "bson": "1000000001640000807ca1a9a012c200", + "extjson": "{\"d\" : -2.0001220703125e10}" + }, + { + "description": "0.0", + "bson": "10000000016400000000000000000000", + "extjson": "{\"d\" : 0.0}" + }, + { + "description": "-0.0", + "bson": "10000000016400000000000000008000", + "extjson": "{\"d\" : -0.0}" + }, + { + "description": "NaN", + "bson": "10000000016400000000000000F87F00" + }, + { + "description": "NaN with payload", + "bson": "10000000016400120000000000F87F00" + }, + { + "description": "Inf", + "bson": "10000000016400000000000000F07F00" + }, + { + "description": "-Inf", + "bson": "10000000016400000000000000F0FF00" + } + ], + "decodeErrors": [ + { + "description": "double truncated", + "bson": "0B0000000164000000F03F00" + } + ] +} diff --git a/spec/support/corpus-tests/failures/binary.json b/spec/support/corpus-tests/failures/binary.json new file mode 100644 index 000000000..9f63fcbd7 --- /dev/null +++ b/spec/support/corpus-tests/failures/binary.json @@ -0,0 +1,69 @@ +{ + "description": "Binary type", + "bson_type": "0x05", + "test_key": "x", + "valid": [ + { + "description": "subtype 0x00 (Zero-length)", + "bson": "0D000000057800000000000000", + "extjson": "{\"x\" : {\"$binary\" : \"\", \"$type\" : \"00\"}}" + }, + { + "description": "subtype 0x00", + "bson": "0F0000000578000200000000FFFF00", + "extjson": "{\"x\" : {\"$binary\" : \"//8=\", \"$type\" : \"00\"}}" + }, + { + "description": "subtype 0x01", + "bson": "0F0000000578000200000001FFFF00", + "extjson": "{\"x\" : {\"$binary\" : \"//8=\", \"$type\" : \"01\"}}" + }, + { + "description": "subtype 0x02", + "bson": "13000000057800060000000202000000ffff00", + "extjson": "{\"x\" : {\"$binary\" : \"//8=\", \"$type\" : \"02\"}}" + }, + { + "description": "subtype 0x03", + "bson": "1D000000057800100000000373FFD26444B34C6990E8E7D1DFC035D400", + "extjson": "{\"x\" : {\"$binary\" : \"c//SZESzTGmQ6OfR38A11A==\", \"$type\" : \"03\"}}" + }, + { + "description": "subtype 0x04", + "bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400", + "extjson": "{\"x\" : {\"$binary\" : \"c//SZESzTGmQ6OfR38A11A==\", \"$type\" : \"04\"}}" + }, + { + "description": "subtype 0x05", + "bson": "1D000000057800100000000573FFD26444B34C6990E8E7D1DFC035D400", + "extjson": "{\"x\" : {\"$binary\" : \"c//SZESzTGmQ6OfR38A11A==\", \"$type\" : \"05\"}}" + }, + { + "description": "subtype 0x80", + "bson": "0F0000000578000200000080FFFF00", + "extjson": "{\"x\" : {\"$binary\" : \"//8=\", \"$type\" : \"80\"}}" + } + ], + "decodeErrors": [ + { + "description": "Length longer than document", + "bson": "1D000000057800FF0000000573FFD26444B34C6990E8E7D1DFC035D400" + }, + { + "description": "Negative length", + "bson": "0D000000057800FFFFFFFF0000" + }, + { + "description": "subtype 0x02 length too long ", + "bson": "13000000057800060000000203000000FFFF00" + }, + { + "description": "subtype 0x02 length too short", + "bson": "13000000057800060000000201000000FFFF00" + }, + { + "description": "subtype 0x02 length negative one", + "bson": "130000000578000600000002FFFFFFFFFFFF00" + } + ] +} diff --git a/spec/support/corpus-tests/failures/datetime.json b/spec/support/corpus-tests/failures/datetime.json new file mode 100644 index 000000000..828c3e977 --- /dev/null +++ b/spec/support/corpus-tests/failures/datetime.json @@ -0,0 +1,31 @@ +{ + "description": "DateTime", + "bson_type": "0x09", + "test_key": "a", + "valid": [ + { + "description": "epoch", + "bson": "10000000096100000000000000000000", + "extjson": "{\"a\" : {\"$date\" : \"1970-01-01T00:00:00.000Z\"}}", + "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"0\"}}}" + }, + { + "description": "positive ms", + "bson": "10000000096100C4D8D6CC3B01000000", + "extjson": "{\"a\" : {\"$date\" : \"2012-12-24T12:15:30.500Z\"}}", + "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"1356351330500\"}}}" + }, + { + "description": "negative", + "bson": "10000000096100C43CE7B9BDFFFFFF00", + "extjson": "{\"a\" : {\"$date\" : \"1960-12-24T12:15:30.500Z\"}}", + "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"-284643869500\"}}}" + } + ], + "decodeErrors": [ + { + "description": "datetime field truncated", + "bson": "0C0000000961001234567800" + } + ] +} diff --git a/spec/support/corpus-tests/failures/dbpointer.json b/spec/support/corpus-tests/failures/dbpointer.json new file mode 100644 index 000000000..b92417e09 --- /dev/null +++ b/spec/support/corpus-tests/failures/dbpointer.json @@ -0,0 +1,42 @@ +{ + "description": "DBPointer type (deprecated)", + "bson_type": "0x0C", + "deprecated": true, + "test_key": "a", + "valid": [ + { + "description": "DBpointer", + "bson": "1A0000000C610002000000620056E1FC72E0C917E9C471416100" + }, + { + "description": "With two-byte UTF-8", + "bson": "1B0000000C610003000000C3A90056E1FC72E0C917E9C471416100" + } + ], + "decodeErrors": [ + { + "description": "String with negative length", + "bson": "1A0000000C6100FFFFFFFF620056E1FC72E0C917E9C471416100" + }, + { + "description": "String with zero length", + "bson": "1A0000000C610000000000620056E1FC72E0C917E9C471416100" + }, + { + "description": "String not null terminated", + "bson": "1A0000000C610002000000626256E1FC72E0C917E9C471416100" + }, + { + "description": "short OID (less than minimum length for field)", + "bson": "160000000C61000300000061620056E1FC72E0C91700" + }, + { + "description": "short OID (greater than minimum, but truncated)", + "bson": "1A0000000C61000300000061620056E1FC72E0C917E9C4716100" + }, + { + "description": "String with bad UTF-8", + "bson": "1A0000000C610002000000E90056E1FC72E0C917E9C471416100" + } + ] +} diff --git a/spec/support/corpus-tests/failures/int64.json b/spec/support/corpus-tests/failures/int64.json new file mode 100644 index 000000000..bae188509 --- /dev/null +++ b/spec/support/corpus-tests/failures/int64.json @@ -0,0 +1,38 @@ +{ + "description": "Int64 type", + "bson_type": "0x12", + "test_key": "a", + "valid": [ + { + "description": "MinValue", + "bson": "10000000126100000000000000008000", + "extjson": "{\"a\" : {\"$numberLong\" : \"-9223372036854775808\"}}" + }, + { + "description": "MaxValue", + "bson": "10000000126100FFFFFFFFFFFFFF7F00", + "extjson": "{\"a\" : {\"$numberLong\" : \"9223372036854775807\"}}" + }, + { + "description": "-1", + "bson": "10000000126100FFFFFFFFFFFFFFFF00", + "extjson": "{\"a\" : {\"$numberLong\" : \"-1\"}}" + }, + { + "description": "0", + "bson": "10000000126100000000000000000000", + "extjson": "{\"a\" : {\"$numberLong\" : \"0\"}}" + }, + { + "description": "1", + "bson": "10000000126100010000000000000000", + "extjson": "{\"a\" : {\"$numberLong\" : \"1\"}}" + } + ], + "decodeErrors": [ + { + "description": "int64 field truncated", + "bson": "0C0000001261001234567800" + } + ] +} diff --git a/spec/support/corpus-tests/failures/regex.json b/spec/support/corpus-tests/failures/regex.json new file mode 100644 index 000000000..abf49b9dd --- /dev/null +++ b/spec/support/corpus-tests/failures/regex.json @@ -0,0 +1,37 @@ +{ + "description": "Regular Expression type", + "bson_type": "0x0B", + "test_key": "a", + "valid": [ + { + "description": "empty regex with no options", + "bson": "0A0000000B6100000000", + "extjson": "{\"a\" : {\"$regex\" : \"\", \"$options\" : \"\"}}" + }, + { + "description": "regex without options", + "bson": "0D0000000B6100616263000000", + "extjson": "{\"a\" : {\"$regex\" : \"abc\", \"$options\" : \"\"}}" + }, + { + "description": "regex with options", + "bson": "0F0000000B610061626300696D0000", + "extjson": "{\"a\" : {\"$regex\" : \"abc\", \"$options\" : \"im\"}}" + }, + { + "description": "regex with slash", + "bson": "110000000B610061622F636400696D0000", + "extjson": "{\"a\" : {\"$regex\" : \"ab/cd\", \"$options\" : \"im\"}}" + } + ], + "decodeErrors": [ + { + "description": "embedded null in pattern", + "bson": "0F0000000B610061006300696D0000" + }, + { + "description": "embedded null in flags", + "bson": "100000000B61006162630069006D0000" + } + ] +} diff --git a/spec/support/corpus-tests/failures/symbol.json b/spec/support/corpus-tests/failures/symbol.json new file mode 100644 index 000000000..6f9f962bb --- /dev/null +++ b/spec/support/corpus-tests/failures/symbol.json @@ -0,0 +1,62 @@ +{ + "description": "Symbol", + "bson_type": "0x0E", + "deprecated": true, + "test_key": "a", + "valid": [ + { + "description": "Empty string", + "bson": "0D0000000E6100010000000000" + }, + { + "description": "Single character", + "bson": "0E0000000E610002000000620000" + }, + { + "description": "Multi-character", + "bson": "190000000E61000D0000006162616261626162616261620000" + }, + { + "description": "two-byte UTF-8 (\u00e9)", + "bson": "190000000E61000D000000C3A9C3A9C3A9C3A9C3A9C3A90000" + }, + { + "description": "three-byte UTF-8 (\u2606)", + "bson": "190000000E61000D000000E29886E29886E29886E298860000" + }, + { + "description": "Embedded nulls", + "bson": "190000000E61000D0000006162006261620062616261620000" + } + ], + "decodeErrors": [ + { + "description": "bad symbol length: 0 (but no 0x00 either)", + "bson": "0C0000000261000000000000" + }, + { + "description": "bad symbol length: -1", + "bson": "0C000000026100FFFFFFFF00" + }, + { + "description": "bad symbol length: eats terminator", + "bson": "10000000026100050000006200620000" + }, + { + "description": "bad symbol length: longer than rest of document", + "bson": "120000000200FFFFFF00666F6F6261720000" + }, + { + "description": "symbol is not null-terminated", + "bson": "1000000002610004000000616263FF00" + }, + { + "description": "empty symbol, but extra null", + "bson": "0E00000002610001000000000000" + }, + { + "description": "invalid UTF-8", + "bson": "0E00000002610002000000E90000" + } + ] +} diff --git a/spec/support/corpus-tests/failures/undefined.json b/spec/support/corpus-tests/failures/undefined.json new file mode 100644 index 000000000..02c9d230e --- /dev/null +++ b/spec/support/corpus-tests/failures/undefined.json @@ -0,0 +1,13 @@ +{ + "description": "Undefined type (deprecated)", + "bson_type": "0x06", + "deprecated": true, + "test_key": "a", + "valid": [ + { + "description": "Undefined", + "bson": "0800000006610000", + "extjson": "{\"a\" : {\"$undefined\" : true}}" + } + ] +} diff --git a/spec/support/corpus-tests/int32.json b/spec/support/corpus-tests/int32.json new file mode 100644 index 000000000..16e945d08 --- /dev/null +++ b/spec/support/corpus-tests/int32.json @@ -0,0 +1,38 @@ +{ + "description": "Int32 type", + "bson_type": "0x10", + "test_key": "i", + "valid": [ + { + "description": "MinValue", + "bson": "0C0000001069000000008000", + "extjson": "{\"i\" : -2147483648}" + }, + { + "description": "MaxValue", + "bson": "0C000000106900FFFFFF7F00", + "extjson": "{\"i\" : 2147483647}" + }, + { + "description": "-1", + "bson": "0C000000106900FFFFFFFF00", + "extjson": "{\"i\" : -1}" + }, + { + "description": "0", + "bson": "0C0000001069000000000000", + "extjson": "{\"i\" : 0}" + }, + { + "description": "1", + "bson": "0C0000001069000100000000", + "extjson": "{\"i\" : 1}" + } + ], + "decodeErrors": [ + { + "description": "Bad int32 field length", + "bson": "090000001061000500" + } + ] +} diff --git a/spec/support/corpus-tests/maxkey.json b/spec/support/corpus-tests/maxkey.json new file mode 100644 index 000000000..ce6d403fe --- /dev/null +++ b/spec/support/corpus-tests/maxkey.json @@ -0,0 +1,12 @@ +{ + "description": "Maxkey type", + "bson_type": "0xFF", + "test_key": "a", + "valid": [ + { + "description": "Maxkey", + "bson": "080000007F610000", + "extjson": "{\"a\" : {\"$maxKey\" : 1}}" + } + ] +} diff --git a/spec/support/corpus-tests/minkey.json b/spec/support/corpus-tests/minkey.json new file mode 100644 index 000000000..97c6b1236 --- /dev/null +++ b/spec/support/corpus-tests/minkey.json @@ -0,0 +1,12 @@ +{ + "description": "Minkey type", + "bson_type": "0xFF", + "test_key": "a", + "valid": [ + { + "description": "Minkey", + "bson": "08000000FF610000", + "extjson": "{\"a\" : {\"$minKey\" : 1}}" + } + ] +} diff --git a/spec/support/corpus-tests/null.json b/spec/support/corpus-tests/null.json new file mode 100644 index 000000000..ee6e8a363 --- /dev/null +++ b/spec/support/corpus-tests/null.json @@ -0,0 +1,12 @@ +{ + "description": "Null type", + "bson_type": "0x0A", + "test_key": "a", + "valid": [ + { + "description": "Null", + "bson": "080000000A610000", + "extjson": "{\"a\" : null}" + } + ] +} diff --git a/spec/support/corpus-tests/oid.json b/spec/support/corpus-tests/oid.json new file mode 100644 index 000000000..417fce6e2 --- /dev/null +++ b/spec/support/corpus-tests/oid.json @@ -0,0 +1,28 @@ +{ + "description": "ObjectId", + "bson_type": "0x07", + "test_key": "a", + "valid": [ + { + "description": "All zeroes", + "bson": "1400000007610000000000000000000000000000", + "extjson": "{\"a\" : {\"$oid\" : \"000000000000000000000000\"}}" + }, + { + "description": "All ones", + "bson": "14000000076100FFFFFFFFFFFFFFFFFFFFFFFF00", + "extjson": "{\"a\" : {\"$oid\" : \"ffffffffffffffffffffffff\"}}" + }, + { + "description": "Random", + "bson": "1400000007610056E1FC72E0C917E9C471416100", + "extjson": "{\"a\" : {\"$oid\" : \"56e1fc72e0c917e9c4714161\"}}" + } + ], + "decodeErrors": [ + { + "description": "OID truncated", + "bson": "1200000007610056E1FC72E0C917E9C471" + } + ] +} diff --git a/spec/support/corpus-tests/string.json b/spec/support/corpus-tests/string.json new file mode 100644 index 000000000..3ec5f224d --- /dev/null +++ b/spec/support/corpus-tests/string.json @@ -0,0 +1,67 @@ +{ + "description": "String", + "bson_type": "0x02", + "test_key": "a", + "valid": [ + { + "description": "Empty string", + "bson": "0D000000026100010000000000", + "extjson": "{\"a\" : \"\"}" + }, + { + "description": "Single character", + "bson": "0E00000002610002000000620000", + "extjson": "{\"a\" : \"b\"}" + }, + { + "description": "Multi-character", + "bson": "190000000261000D0000006162616261626162616261620000", + "extjson": "{\"a\" : \"abababababab\"}" + }, + { + "description": "two-byte UTF-8 (\u00e9)", + "bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", + "extjson": "{\"a\" : \"\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\"}" + }, + { + "description": "three-byte UTF-8 (\u2606)", + "bson": "190000000261000D000000E29886E29886E29886E298860000", + "extjson": "{\"a\" : \"\\u2606\\u2606\\u2606\\u2606\"}" + }, + { + "description": "Embedded nulls", + "bson": "190000000261000D0000006162006261620062616261620000", + "extjson": "{\"a\" : \"ab\\u0000bab\\u0000babab\"}" + } + ], + "decodeErrors": [ + { + "description": "bad string length: 0 (but no 0x00 either)", + "bson": "0C0000000261000000000000" + }, + { + "description": "bad string length: -1", + "bson": "0C000000026100FFFFFFFF00" + }, + { + "description": "bad string length: eats terminator", + "bson": "10000000026100050000006200620000" + }, + { + "description": "bad string length: longer than rest of document", + "bson": "120000000200FFFFFF00666F6F6261720000" + }, + { + "description": "string is not null-terminated", + "bson": "1000000002610004000000616263FF00" + }, + { + "description": "empty string, but extra null", + "bson": "0E00000002610001000000000000" + }, + { + "description": "invalid UTF-8", + "bson": "0E00000002610002000000E90000" + } + ] +} diff --git a/spec/support/corpus-tests/timestamp.json b/spec/support/corpus-tests/timestamp.json new file mode 100644 index 000000000..667d6b303 --- /dev/null +++ b/spec/support/corpus-tests/timestamp.json @@ -0,0 +1,18 @@ +{ + "description": "Timestamp type", + "bson_type": "0x11", + "test_key": "a", + "valid": [ + { + "description": "Timestamp: (123456789, 42)", + "bson": "100000001161002A00000015CD5B0700", + "extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : 42}}}" + } + ], + "decodeErrors": [ + { + "description": "Truncated timestamp field", + "bson": "0f0000001161002A00000015CD5B00" + } + ] +} diff --git a/spec/support/corpus-tests/top.json b/spec/support/corpus-tests/top.json new file mode 100644 index 000000000..38f184373 --- /dev/null +++ b/spec/support/corpus-tests/top.json @@ -0,0 +1,62 @@ +{ + "description": "Top-level document validity", + "bson_type": "0x00", + "decodeErrors": [ + { + "description": "An object size that's too small to even include the object size, but is a well-formed, empty object", + "bson": "0100000000" + }, + { + "description": "An object size that's only enough for the object size, but is a well-formed, empty object", + "bson": "0400000000" + }, + { + "description": "One object, with length shorter than size (missing EOO)", + "bson": "05000000" + }, + { + "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x01", + "bson": "0500000001" + }, + { + "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0xff", + "bson": "05000000FF" + }, + { + "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x70", + "bson": "0500000070" + }, + { + "description": "Byte count is zero (with non-zero input length)", + "bson": "00000000000000000000" + }, + { + "description": "Stated length exceeds byte count, with truncated document", + "bson": "1200000002666F6F0004000000626172" + }, + { + "description": "Stated length less than byte count, with garbage after envelope", + "bson": "1200000002666F6F00040000006261720000DEADBEEF" + }, + { + "description": "Stated length exceeds byte count, with valid envelope", + "bson": "1300000002666F6F00040000006261720000" + }, + { + "description": "Stated length less than byte count, with valid envelope", + "bson": "1100000002666F6F00040000006261720000" + }, + { + "description": "Invalid BSON type low range", + "bson": "07000000000000" + }, + { + "description": "Invalid BSON type high range", + "bson": "07000000800000" + }, + { + "description": "Document truncated mid-key", + "bson": "1200000002666F" + } + ] +} diff --git a/spec/support/corpus.rb b/spec/support/corpus.rb new file mode 100644 index 000000000..5119c758b --- /dev/null +++ b/spec/support/corpus.rb @@ -0,0 +1,249 @@ +# Copyright (C) 2016 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'json' + +module BSON + module Corpus + + # Represents a test from the driver BSON Corpus. + # + # @since 4.2.0 + class Spec + + # The spec description. + # + # @return [ String ] The spec description. + # + # @since 4.2.0 + attr_reader :description + + # The document key of the object to test. + # + # @return [ String ] The document key. + # + # @since 4.2.0 + attr_reader :test_key + + # Instantiate the new spec. + # + # @example Create the spec. + # Spec.new(file) + # + # @param [ String ] file The name of the json file. + # + # @since 4.2.0 + def initialize(file) + @spec = ::JSON.parse(File.read(file)) + @valid = @spec['valid'] || [] + @description = @spec['description'] + @test_key = @spec['test_key'] + end + + # Get a list of tests that don't raise exceptions. + # + # @example Get the list of valid tests. + # spec.valid_tests + # + # @return [ Array ] The list of valid Tests. + # + # @since 4.2.0 + def valid_tests + @valid_tests ||= + @valid.collect do |test| + BSON::Corpus::Test.new(self, test) + end + end + + # The class of the bson object to test. + # + # @example Get the class of the object to test. + # spec.klass + # + # @return [ Class ] The object class. + # + # @since 4.2.0 + def klass + @klass ||= BSON.const_get(description) + end + end + + # Represents a single BSON Corpus test. + # + # @since 4.2.0 + class Test + + # The test description. + # + # @return [ String ] The test description. + # + # @since 4.2.0 + attr_reader :description + + # Name of a field in a valid test case extjson document that should be + # checked against the case's string field. + # + # @return [ String ] The json representation of the object. + # + # @since 4.2.0 + attr_reader :test_key + + # Instantiate the new Test. + # + # @example Create the test. + # Test.new(test) + # + # @param [ Corpus::Spec ] spec The test specification. + # @param [ Hash ] test The test specification. + # + # @since 4.2.0 + def initialize(spec, test) + @spec = spec + @description = test['description'] + @canonical_bson = test['canonical_bson'] + @extjson = ::JSON.parse(test['extjson']) if test['extjson'] + @bson = test['bson'] + @test_key = spec.test_key + end + + # The correct representation of the subject as bson. + # + # @example Get the correct representation of the subject as bson. + # test.correct_bson + # + # @return [ String ] The correct bson bytes. + # + # @since 4.2.0 + def correct_bson + @correct_bson ||= decode_hex(@canonical_bson || @bson) + end + + # Given the hex representation of bson, decode it into a Document, + # then reencoded it to bson. + # + # @example Decoded the bson hex representation, then reencode. + # test.reencoded_bson + # + # @return [ String ] The reencoded bson bytes. + # + # @since 4.2.0 + def reencoded_bson + bson_bytes = decode_hex(@bson) + buffer = BSON::ByteBuffer.new(bson_bytes) + BSON::Document.from_bson(buffer).to_bson.to_s + end + + # Given the hex representation of the canonical bson, decode it into a Document, + # then reencoded it to bson. + # + # @example Decoded the canonical bson hex representation, then reencode. + # test.reencoded_canonical_bson + # + # @return [ String ] The reencoded canonical bson bytes. + # + # @since 4.2.0 + def reencoded_canonical_bson + bson_bytes = decode_hex(@canonical_bson) + buffer = BSON::ByteBuffer.new(bson_bytes) + BSON::Document.from_bson(buffer).to_bson.to_s + end + + # Whether the canonical bson should be tested. + # + # @example Determine if the canonical bson should be tested. + # test.test_canonical_bson? + # + # @return [ true, false ] Whether the canonical bson should be tested. + # + # @since 4.2.0 + def test_canonical_bson? + @canonical_bson && (@bson != @canonical_bson) + end + + # The correct representation of the subject as extended json. + # + # @example Get the correct representation of the subject as extended json. + # test.correct_extjson + # + # @return [ String ] The correct extended json representation. + # + # @since 4.2.0 + def correct_extjson + @canonical_extjson || @extjson + end + + # Whether the extended json should be tested. + # + # @example Determine if the extended json should be tested. + # test.test_extjson? + # + # @return [ true, false ] Whether the extended json should be tested. + # + # @since 4.2.0 + def test_extjson? + !!@extjson + end + + # Get the extended json representation of the decoded doc from the provided + # bson hex representation. + # + # @example Get the extended json representation of the decoded doc. + # test.extjson_from_encoded_bson + # + # @return [ Hash ] The extended json representation. + # + # @since 4.2.0 + def extjson_from_bson + subject = decode_hex(@bson) + buffer = BSON::ByteBuffer.new(subject) + ::JSON.parse(BSON::Document.from_bson(buffer).to_json) + end + + # Get the extended json representation of the decoded doc from the provided + # canonical bson hex representation. + # + # @example Get the extended json representation of the canonical decoded doc. + # test.extjson_from_canonical_bson + # + # @return [ Hash ] The extended json representation. + # + # @since 4.2.0 + def extjson_from_canonical_bson + subject = decode_hex(@canonical_bson) + buffer = BSON::ByteBuffer.new(subject) + ::JSON.parse(BSON::Document.from_bson(buffer).to_json) + end + + # Get the extended json representation of the decoded doc from the provided + # extended json representation. (Verifies roundtrip) + # + # @example Get the extended json representation of the canonical decoded doc. + # test.extjson_from_encoded_extjson + # + # @return [ Hash ] The extended json representation. + # + # @since 4.2.0 + def extjson_from_encoded_extjson + doc = BSON::Document.new(@extjson) + ::JSON.parse(doc.to_json) + end + + private + + def decode_hex(obj) + [ obj ].pack('H*') + end + end + end +end