from std/marshal import
dump,
load,
safe_to_dump,
MarshallingException,
UnmarshallingException;
from std/data/xml import XML;
from std/internals import object_slots, ref_id;
from std/io try import Path;
from std/string/base64 import decode, encode;
from std/time import Time, TimeZone;
from test/more import *;
__global__{marshal_phase11_builds} := 0;
__global__{marshal_hook_log} := "";
__global__{marshal_on_dump_count} := 0;
__global__{marshal_on_load_count} := 0;
class MarshalPhase11Box {
let String name with get, set := "unset";
const kind := "box";
let payload;
method __build__ () {
__global__{marshal_phase11_builds} :=
__global__{marshal_phase11_builds} + 1;
}
method label () {
return name _ ":" _ kind;
}
}
class MarshalHookBox {
let name := "";
let status := "new";
let peer;
method __on_dump__ () {
__global__{marshal_hook_log} :=
__global__{marshal_hook_log} _ "dump:" _ name _ ";";
__global__{marshal_on_dump_count} :=
__global__{marshal_on_dump_count} + 1;
status := "dumped";
}
method __on_load__ () {
__global__{marshal_hook_log} :=
__global__{marshal_hook_log} _ "load:" _ name _ ";";
__global__{marshal_on_load_count} :=
__global__{marshal_on_load_count} + 1;
status := status _ ":loaded";
if ( peer ≢ null ) {
status := status _ ":peer=" _ peer{name};
}
}
}
class MarshalBadDump {
method __on_dump__ () {
die "bad dump hook";
}
}
class MarshalBadLoad {
method __on_load__ () {
die "bad load hook";
}
}
function marshal_add_one (x) {
return x + 1;
}
const marshal_bonus := 10;
let marshal_add_bonus := function (x) {
return x + marshal_bonus;
};
function marshal_add_two (x) {
return marshal_add_one( marshal_add_one(x) );
}
function marshal_load_message (blob) {
let message := "";
try {
load( decode(blob) );
}
catch ( UnmarshallingException e ) {
message := e{message};
}
return message;
}
const marshal_phase17_offset := 40;
class MarshalPhase17Point {
let Number x := 1;
const String label := "point";
method total ( Number y ) -> Number {
return x + y + marshal_phase17_offset;
}
static method tag () -> String {
return "point-class";
}
}
class MarshalPhase17Base {
let Number base := 40;
method base_value () -> Number {
return base;
}
}
class MarshalPhase17Child extends MarshalPhase17Base {
let Number extra := 2;
method total () -> Number {
return self.base_value() + extra;
}
}
class MarshalPhase17Outer {
class Inner {
let Number value;
method get_value () -> Number {
return value;
}
}
static method build ( Number value ) {
return new self{"Inner"}( value: value );
}
}
const marshal_phase18_prefix := "label:";
trait MarshalPhase18Labelled {
method label () -> String {
return marshal_phase18_prefix _ self.get_name();
}
}
class MarshalPhase18Thing with MarshalPhase18Labelled {
let String name with get := "Ada";
}
is( typeof dump, "Function", "dump is exported" );
is( typeof load, "Function", "load is exported" );
is( typeof safe_to_dump, "Function", "safe_to_dump is exported" );
is( typeof dump(null), "BinaryString", "dump returns BinaryString" );
is( load( dump(null) ), null, "null round trip" );
is( load( dump(true) ), true, "true round trip" );
is( load( dump(false) ), false, "false round trip" );
is( load( dump(42) ), 42, "integer number round trip" );
is( load( dump(-17) ), -17, "negative integer round trip" );
is( load( dump(3.25) ), 3.25, "float number round trip" );
is( load( dump("hello") ), "hello", "String round trip" );
let raw := ~to_binary("ABC");
let raw_roundtrip := load( dump(raw) );
is( typeof raw_roundtrip, "BinaryString", "BinaryString round trip type" );
ok( raw_roundtrip == raw, "BinaryString round trip payload" );
let ascii_binary := to_binary("ABC");
is(
typeof load( dump(ascii_binary) ),
"BinaryString",
"ASCII BinaryString does not load as String",
);
is( load( dump("ABC") ), "ABC", "ASCII String still loads as String" );
is(
encode( dump([ 1, [ 2 ], "x" ]) ),
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIKCAoMBggABYXiCAoECgA==",
"strong-only dump fixture remains byte-compatible",
);
is( safe_to_dump(42), true, "safe_to_dump accepts scalar values" );
is( safe_to_dump([ 1, [ 2 ] ]), true, "safe_to_dump accepts arrays" );
is( safe_to_dump({ foo: 1 }), true, "safe_to_dump accepts Dicts" );
is(
safe_to_dump( {{ foo: 1, foo: 2 }} ),
true,
"safe_to_dump accepts PairLists",
);
is( safe_to_dump( << 1, 2 >> ), true, "safe_to_dump accepts Sets" );
is( safe_to_dump( <<< 1, 2, 2 >>> ), true, "safe_to_dump accepts Bags" );
is(
safe_to_dump( new Pair( pair: [ "key", "value" ] ) ),
true,
"safe_to_dump accepts Pairs",
);
is( safe_to_dump( new Time(42) ), true, "safe_to_dump accepts Time" );
if ( Path ≢ null ) {
is(
safe_to_dump( new Path("tmp/../file.txt") ),
true,
"safe_to_dump accepts Path",
);
}
is(
safe_to_dump( new MarshalPhase11Box( name: "safe" ) ),
true,
"safe_to_dump accepts user objects",
);
is( safe_to_dump(dump), false, "safe_to_dump rejects native functions" );
is(
safe_to_dump(marshal_add_one),
true,
"safe_to_dump accepts user functions",
);
is(
safe_to_dump(MarshalPhase17Point),
true,
"safe_to_dump accepts user classes",
);
is(
safe_to_dump(MarshalPhase18Labelled),
true,
"safe_to_dump accepts user traits",
);
is(
safe_to_dump( XML.parse("<root/>") ),
false,
"safe_to_dump rejects unsupported runtime-backed objects",
);
let nested := load( dump([ 1, [ 2, 3 ], "four" ]) );
is( typeof nested, "Array", "array root round trip type" );
is( nested[0], 1, "array scalar item round trip" );
is( nested[1][0], 2, "nested array item round trip" );
is( nested[2], "four", "array string item round trip" );
let shared := [ 7 ];
let shared_graph := load( dump([ shared, shared ]) );
is(
ref_id( shared_graph[0] ),
ref_id( shared_graph[1] ),
"shared array references preserve identity",
);
let cycle := [];
cycle.push(cycle);
is(
safe_to_dump(cycle),
true,
"safe_to_dump accepts cyclic arrays",
);
let cycle_roundtrip := load( dump(cycle) );
is(
ref_id(cycle_roundtrip),
ref_id( cycle_roundtrip[0] ),
"self-cyclic array preserves identity",
);
let a := [];
let b := [a];
a.push(b);
a.push(42);
let a2 := load( dump(a) );
is(
a2[0][0][0][0][1],
42,
"cyclic array graph reaches value after round trip",
);
is(
ref_id(a2),
ref_id( a2[0][0] ),
"cyclic array graph preserves root identity",
);
let dict_roundtrip := load( dump({ beta: 2, alpha: 1 }) );
is( typeof dict_roundtrip, "Dict", "Dict round trip type" );
is( dict_roundtrip.get("alpha"), 1, "Dict key lookup after round trip" );
is( dict_roundtrip.sorted_keys(), [ "alpha", "beta" ], "Dict keys survive" );
let dict_shared := [ "shared" ];
let dict_graph := load( dump({ left: dict_shared, right: dict_shared }) );
is(
ref_id( dict_graph{left} ),
ref_id( dict_graph{right} ),
"Dict nested references preserve identity",
);
let cyclic_dict := {};
cyclic_dict.set( "self", cyclic_dict );
let cyclic_dict_roundtrip := load( dump(cyclic_dict) );
is(
ref_id(cyclic_dict_roundtrip),
ref_id( cyclic_dict_roundtrip{self} ),
"cyclic Dict preserves identity",
);
let pairlist_roundtrip := load(
dump( {{ foo: 1, bar: 2, foo: 3, foo: 4 }} ),
);
is( typeof pairlist_roundtrip, "PairList", "PairList round trip type" );
is(
pairlist_roundtrip.keys(),
[ "foo", "bar", "foo", "foo" ],
"PairList key order and duplicates survive",
);
is(
pairlist_roundtrip.get_all("foo"),
[ 1, 3, 4 ],
"PairList duplicate values survive",
);
let pairlist_shared := [ "shared" ];
let pairlist_graph := load(
dump( {{ left: pairlist_shared, right: pairlist_shared }} ),
);
is(
ref_id( pairlist_graph.get("left") ),
ref_id( pairlist_graph.get("right") ),
"PairList nested references preserve identity",
);
let cyclic_pairlist := new PairList();
cyclic_pairlist.add( "self", cyclic_pairlist );
let cyclic_pairlist_roundtrip := load( dump(cyclic_pairlist) );
is(
ref_id(cyclic_pairlist_roundtrip),
ref_id( cyclic_pairlist_roundtrip.get("self") ),
"cyclic PairList preserves identity",
);
let set_roundtrip := load( dump( << 1, 2, 2, 3 >> ) );
is( typeof set_roundtrip, "Set", "Set round trip type" );
is( set_roundtrip.length(), 3, "Set round trip keeps unique members" );
is( set_roundtrip.contains(2), 1, "Set membership survives round trip" );
let set_from_duplicate_payload := load(
decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCBYMBAQKA"),
);
is(
set_from_duplicate_payload.length(),
2,
"Set load applies normal Set duplicate handling",
);
let set_shared := [ "shared" ];
let set_graph := load( dump( << set_shared, [ set_shared ] >> ) );
let set_items := set_graph.to_Array();
is(
ref_id( set_items[0] ),
ref_id( set_items[1][0] ),
"Set nested references preserve identity",
);
let bag_roundtrip := load( dump( <<< 1, 2, 2, 3 >>> ) );
is( typeof bag_roundtrip, "Bag", "Bag round trip type" );
is( bag_roundtrip.count(2), 2, "Bag duplicate count survives round trip" );
is(
bag_roundtrip.to_Array(),
[ 1, 2, 2, 3 ],
"Bag order and duplicate values survive round trip",
);
let bag_shared := [ "shared" ];
let bag_graph := load( dump( <<< bag_shared, bag_shared, [ bag_shared ] >>> ) );
let bag_items := bag_graph.to_Array();
is(
ref_id( bag_items[0] ),
ref_id( bag_items[1] ),
"Bag repeated references preserve identity",
);
is(
ref_id( bag_items[0] ),
ref_id( bag_items[2][0] ),
"Bag nested references preserve identity",
);
let pair_shared := [ "shared" ];
let pair := new Pair( pair: [ pair_shared, [ pair_shared ] ] );
let pair_roundtrip := load( dump(pair) );
is( typeof pair_roundtrip, "Pair", "Pair round trip type" );
is( typeof pair_roundtrip.key, "String", "Pair key round trip type is String" );
is(
typeof pair_roundtrip.value[0],
"Array",
"Pair value nested item type survives",
);
is(
pair_roundtrip.value[0][0],
"shared",
"Pair value nested reference survives",
);
let number_key_pair := load( dump( new Pair( pair: [ 42, "answer" ] ) ) );
is( number_key_pair.key, "42", "Pair number key survives as String" );
is( number_key_pair.value, "answer", "Pair value survives" );
let repeated_pair_graph := load( dump([ pair, pair ]) );
is(
ref_id( repeated_pair_graph[0] ),
ref_id( repeated_pair_graph[1] ),
"repeated Pair identity is preserved",
);
let time_roundtrip := load( dump( new Time(12345) ) );
is( typeof time_roundtrip, "Time", "Time round trip type" );
is( time_roundtrip.epoch(), 12345, "Time epoch survives round trip" );
let zoned_time_roundtrip := load(
dump( new Time( 1778241600, timezone: TimeZone.named("Europe/London") ) ),
);
is(
zoned_time_roundtrip.timezone().to_String(),
"Europe/London",
"Time timezone survives round trip",
);
let repeated_time_graph := load( dump([ time_roundtrip, time_roundtrip ]) );
is(
ref_id( repeated_time_graph[0] ),
ref_id( repeated_time_graph[1] ),
"repeated Time identity is preserved",
);
if ( Path ≢ null ) {
let path_roundtrip := load( dump( new Path("tmp/../file.txt") ) );
is( typeof path_roundtrip, "Path", "Path round trip type" );
is(
path_roundtrip.to_String(),
"tmp/../file.txt",
"Path exact string form survives round trip",
);
let repeated_path_graph := load( dump([ path_roundtrip, path_roundtrip ]) );
is(
ref_id( repeated_path_graph[0] ),
ref_id( repeated_path_graph[1] ),
"repeated Path identity is preserved",
);
}
let function_roundtrip := load( dump(marshal_add_one) );
is( typeof function_roundtrip, "Function", "Function round trip type" );
is( function_roundtrip(41), 42, "Function round trip executes" );
let function_capture_roundtrip := load( dump(marshal_add_bonus) );
is(
function_capture_roundtrip(32),
42,
"Function scalar const capture survives round trip",
);
let function_dependency_roundtrip := load( dump(marshal_add_two) );
is(
function_dependency_roundtrip(40),
42,
"Function internal dependency survives round trip",
);
let class_roundtrip := load( dump(MarshalPhase17Point) );
is( typeof class_roundtrip, "Class", "Class round trip type" );
is(
class_roundtrip.tag(),
"point-class",
"Class static method survives round trip",
);
let class_object := new class_roundtrip( x: 1 );
is(
typeof class_object,
"MarshalPhase17Point",
"Class round trip creates object with original type name",
);
is(
class_object.total(1),
42,
"Class method scalar const capture survives round trip",
);
is(
class_object{label},
"point",
"Class const typed field survives round trip",
);
let child_class_roundtrip := load( dump(MarshalPhase17Child) );
let child_object := new child_class_roundtrip();
is(
child_object.total(),
42,
"Class parent dependency survives round trip",
);
is(
child_object.base_value(),
40,
"Class inherited method survives round trip",
);
let outer_class_roundtrip := load( dump(MarshalPhase17Outer) );
let inner_object := outer_class_roundtrip.build(42);
is(
inner_object.get_value(),
42,
"Class nested class survives round trip",
);
let trait_roundtrip := load( dump(MarshalPhase18Labelled) );
class MarshalPhase18LoadedTraitUser with trait_roundtrip {
let String name with get := "Bea";
}
let loaded_trait_object := new MarshalPhase18LoadedTraitUser();
is(
loaded_trait_object.label(),
"label:Bea",
"Trait method survives direct trait round trip",
);
let trait_class_roundtrip := load( dump(MarshalPhase18Thing) );
let trait_class_object := new trait_class_roundtrip();
is(
trait_class_object.label(),
"label:Ada",
"Class trait dependency survives round trip",
);
__global__{marshal_phase11_builds} := 0;
let object_shared := [ "shared" ];
let object := new MarshalPhase11Box(
name: "Ada",
payload: [ object_shared, [ object_shared ] ],
);
is( __global__{marshal_phase11_builds}, 1, "new calls user __build__" );
let object_roundtrip := load( dump(object) );
is(
__global__{marshal_phase11_builds},
1,
"load does not call user __build__",
);
is(
typeof object_roundtrip,
"MarshalPhase11Box",
"user object round trip type",
);
is( object_roundtrip{name}, "Ada", "user object string slot survives" );
is( object_roundtrip{kind}, "box", "user object const slot survives" );
is( object_roundtrip.label(), "Ada:box", "user object methods still work" );
is(
ref_id( object_roundtrip{payload}[0] ),
ref_id( object_roundtrip{payload}[1][0] ),
"user object nested references preserve identity",
);
let repeated_object_graph := load( dump([ object, object ]) );
is(
ref_id( repeated_object_graph[0] ),
ref_id( repeated_object_graph[1] ),
"repeated user object identity is preserved",
);
object_roundtrip.set_name("Bea");
is(
object_roundtrip.label(),
"Bea:box",
"user object field accessor metadata survives",
);
like(
exception( function () {
object_roundtrip.set_name(123);
} ),
/must be String/,
"loaded object typed setter metadata survives",
);
let bound_target := new MarshalPhase11Box( name: "Callie" );
let bound_method := bound_target{"label"};
is( typeof bound_method, "Method", "object dict access exposes bound methods" );
is( safe_to_dump(bound_method), true, "safe_to_dump accepts bound methods" );
let bound_method_roundtrip := load( dump(bound_method) );
is( typeof bound_method_roundtrip, "Method", "bound method round trip type" );
is(
bound_method_roundtrip(),
"Callie:box",
"bound method round trip calls original receiver method",
);
let bound_method_graph := load(
dump([ bound_target, bound_method, bound_method ]),
);
is(
ref_id( bound_method_graph[1] ),
ref_id( bound_method_graph[2] ),
"repeated bound method identity is preserved",
);
bound_method_graph[0].set_name("Dana");
is(
bound_method_graph[1](),
"Dana:box",
"bound method receiver is loaded as a shared strong reference",
);
__global__{marshal_hook_log} := "";
__global__{marshal_on_dump_count} := 0;
__global__{marshal_on_load_count} := 0;
let hook_obj := new MarshalHookBox( name: "one" );
let hook_roundtrip := load( dump([ hook_obj, hook_obj ]) );
is(
__global__{marshal_on_dump_count},
1,
"__on_dump__ runs once per object identity",
);
is(
hook_obj{status},
"dumped",
"__on_dump__ may mutate the object before slot encoding",
);
is(
__global__{marshal_on_load_count},
1,
"__on_load__ runs once per loaded object identity",
);
is(
hook_roundtrip[0]{status},
"dumped:loaded",
"__on_load__ sees state serialized after __on_dump__",
);
is(
ref_id( hook_roundtrip[0] ),
ref_id( hook_roundtrip[1] ),
"lifecycle hooks preserve repeated object identity",
);
__global__{marshal_hook_log} := "";
__global__{marshal_on_dump_count} := 0;
__global__{marshal_on_load_count} := 0;
let first_hook := new MarshalHookBox( name: "first" );
let second_hook := new MarshalHookBox(
name: "second",
peer: first_hook,
);
let ordered_hooks := load( dump([ first_hook, second_hook ]) );
is(
__global__{marshal_hook_log},
"dump:first;dump:second;load:first;load:second;",
"marshal lifecycle hooks run in object id order",
);
is(
ordered_hooks[1]{status},
"dumped:loaded:peer=first",
"__on_load__ runs after references are reconstructed",
);
let bad_dump_message := "";
try {
dump( new MarshalBadDump() );
}
catch ( MarshallingException e ) {
bad_dump_message := e{message};
}
like(
bad_dump_message,
/__on_dump__.*bad dump hook/,
"__on_dump__ errors are wrapped as MarshallingException",
);
let bad_load_message := "";
try {
load( dump( new MarshalBadLoad() ) );
}
catch ( UnmarshallingException e ) {
bad_load_message := e{message};
}
like(
bad_load_message,
/__on_load__.*bad load hook/,
"__on_load__ errors are wrapped as UnmarshallingException",
);
__global__{marshal_hook_log} := "";
__global__{marshal_on_dump_count} := 0;
let safe_hook := new MarshalHookBox( name: "safe-hook" );
is(
safe_to_dump(safe_hook),
true,
"safe_to_dump accepts supported hook objects",
);
is(
__global__{marshal_on_dump_count},
1,
"safe_to_dump calls __on_dump__",
);
is(
safe_hook{status},
"dumped",
"safe_to_dump preserves __on_dump__ mutation behaviour",
);
is(
safe_to_dump( new MarshalBadDump() ),
false,
"safe_to_dump returns false for __on_dump__ failure",
);
__global__{marshal_on_dump_count} := 0;
let strong_edge_hook := new MarshalHookBox( name: "strong-edge" );
dump([ strong_edge_hook ]);
is(
__global__{marshal_on_dump_count},
1,
"__on_dump__ runs for objects reached through a strong edge",
);
let dump_message := "";
try {
dump(/unsupported/);
}
catch ( MarshallingException e ) {
dump_message := e{message};
}
like(
dump_message,
/not marshalable/,
"dump rejects unsupported objects with MarshallingException",
);
let load_type_message := "";
try {
load("not binary");
}
catch ( TypeException e ) {
load_type_message := e{message};
}
like(
load_type_message,
/expects BinaryString/,
"load rejects non-BinaryString input",
);
let no_tag_message := "";
try {
load( decode("9g==") );
}
catch ( UnmarshallingException e ) {
no_tag_message := e{message};
}
like(
no_tag_message,
/Top-level item is not tag 55799/,
"load rejects an untagged CBOR item",
);
like(
marshal_load_message("9gA="),
/trailing bytes after item/,
"load rejects invalid CBOR with trailing bytes",
);
like(
marshal_load_message("2dn3hnBOT1QtWlVaVS1NQVJTSEFMAaD2gIA="),
/Envelope magic is invalid/,
"load rejects wrong envelope magic",
);
like(
marshal_load_message("2dn3hWxaVVpVLU1BUlNIQUwBoPaA"),
/Envelope must contain exactly 6 fields/,
"load rejects wrong envelope arity",
);
like(
marshal_load_message("2dn3hmxaVVpVLU1BUlNIQUwBgPaAgA=="),
/Envelope options must be a map/,
"load rejects wrong envelope options",
);
let version_message := "";
try {
load( decode("2dn3hmxaVVpVLU1BUlNIQUwCoPaAgA==") );
}
catch ( UnmarshallingException e ) {
version_message := e{message};
}
like(
version_message,
/Unsupported Zuzu Marshal version/,
"load rejects unsupported envelope version",
);
let root_message := "";
try {
load( decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAICA") );
}
catch ( UnmarshallingException e ) {
root_message := e{message};
}
like(
root_message,
/outside the object table/,
"load rejects references outside the object table",
);
let weak_root_message := "";
try {
load( decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIB9oCA") );
}
catch ( UnmarshallingException e ) {
weak_root_message := e{message};
}
like(
weak_root_message,
/Envelope root weak storage record is not allowed here/,
"load rejects weak storage records at the envelope root",
);
let weak_array_value := load(
decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCAoGCAfaA"),
);
is(
weak_array_value[0],
null,
"load accepts weak Array null records",
);
let nested_weak_message := "";
try {
load( decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCAoGCAYIB9oA=") );
}
catch ( UnmarshallingException e ) {
nested_weak_message := e{message};
}
like(
nested_weak_message,
/nested weak storage records are invalid/,
"load rejects nested weak storage records",
);
let weak_class_message := "";
try {
load( decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCB4KCAfaAgA==") );
}
catch ( UnmarshallingException e ) {
weak_class_message := e{message};
}
like(
weak_class_message,
/Object payload 0 class weak storage record is not allowed here/,
"load rejects weak storage records in object class position",
);
like(
marshal_load_message(
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIKCB4KCAAGAggKAgA==",
),
/Object payload 0 class must resolve to a Class/,
"load rejects object class references that do not resolve to classes",
);
let missing_bound_method_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIOCC4KCAAFnbWlzc2luZ4IH" _
"goIAAoCCCYEAgYUCbkJvdW5kTWV0aG9kQm94eDljbGFzcyBCb3Vu" _
"ZE1ldGhvZEJveCB7IG1ldGhvZCBsYWJlbCAoKSB7IHJldHVybiAi" _
"b2siOyB9IH2AgA==";
like(
marshal_load_message(missing_bound_method_blob),
/Bound method object payload 0 method 'missing' was not found/,
"load rejects bound method payloads naming missing methods",
);
let weak_bound_method_receiver_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIOCC4KCAfZlbGFiZWyCB4KC" _
"AAKAggmBAIGFAm5Cb3VuZE1ldGhvZEJveHg5Y2xhc3MgQm91bmRN" _
"ZXRob2RCb3ggeyBtZXRob2QgbGFiZWwgKCkgeyByZXR1cm4gIm9r" _
"IjsgfSB9gIA=";
like(
marshal_load_message(weak_bound_method_receiver_blob),
/Bound method object payload 0 receiver weak storage record is not allowed here/,
"load rejects weak storage records in bound method receiver position",
);
like(
marshal_load_message(
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCAYKCAfZhdoA=",
),
/Pair object payload 0 key must be a text string/,
"load rejects weak storage records in Pair key position",
);
like(
marshal_load_message(
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCAoGBAYA=",
),
/array must be \[0, id\] or \[1, value\]/,
"load rejects bad-arity weak storage records",
);
let weak_dict_value := load(
decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCA4GCYWuCAWF4gA=="),
);
is(
weak_dict_value{"k"},
"x",
"load accepts weak Dict value records",
);
let weak_pairlist_value := load(
decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCBIGCYWuCAWF4gA=="),
);
is(
weak_pairlist_value{"k"},
"x",
"load accepts weak PairList value records",
);
let weak_set_value := load(
decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCBYGCAYIAAIA="),
);
is(
weak_set_value.length(),
1,
"load accepts weak Set member references",
);
like(
marshal_load_message(
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCBoGCAYIAGGOA",
),
/outside the object table/,
"load rejects bad ids inside weak storage records",
);
let weak_object_slot_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAYKCCYEAggeCggAAgYJjZm9v" _
"ggH2gYUCa1dlYWtTbG90Qm94cmNsYXNzIFdlYWtTbG90Qm94O4CA";
let weak_object_slot_value := load( decode(weak_object_slot_blob) );
is(
object_slots(weak_object_slot_value){"foo"},
null,
"load accepts weak object slot records",
);
let weak_code_capture_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCCIEAgYUBYWZ4G2Z1bmN0" _
"aW9uICgpIHsgcmV0dXJuIGNhcDsgfYGCY2NhcIIB9oA=";
like(
marshal_load_message(weak_code_capture_blob),
/Capture 'cap' in code record 0 weak storage record is not allowed here/,
"load rejects weak storage records in code captures",
);
like(
marshal_load_message(
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCCIGCAfaA",
),
/Function object payload 0 code id weak storage record is not allowed here/,
"load rejects weak storage records in code id positions",
);
let duplicate_dict_message := "";
try {
load( decode("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCA4KCY2R1cAGCY2R1cAKA") );
}
catch ( UnmarshallingException e ) {
duplicate_dict_message := e{message};
}
like(
duplicate_dict_message,
/duplicate key 'dup'/,
"load rejects duplicate Dict payload keys",
);
like(
marshal_load_message("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCGGOAgA=="),
/Unsupported object kind 99/,
"load rejects unsupported object kinds",
);
let duplicate_slot_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIKCB4KCAAGCgmF4AYJheAKC" _
"CYEAgYUCbU1hcnNoYWxCYWRCb3h4P2NsYXNzIE1hcnNoYWxCYWR" _
"Cb3ggeyBsZXQgeDsgbWV0aG9kIGxhYmVsICgpIHsgcmV0dXJuI" _
"CJvayI7IH0gfYCA";
like(
marshal_load_message(duplicate_slot_blob),
/Object payload 0 contains duplicate slot 'x'/,
"load rejects duplicate object slot names",
);
like(
marshal_load_message("2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCCIEBgA=="),
/Function object payload 0 code id is outside the code table/,
"load rejects invalid function code references",
);
let unsupported_code_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoPaAgYUYY25tYXJzaGFsX2JhZF9" _
"mbngZZnVuY3Rpb24gKCkgeyByZXR1cm4gMTsgfYCA";
like(
marshal_load_message(unsupported_code_blob),
/Unsupported code kind 99/,
"load rejects unsupported code records",
);
let invalid_code_dependency_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoPaAgYUCbU1hcnNoYWxCYWRCb3h" _
"4P2NsYXNzIE1hcnNoYWxCYWRCb3ggeyBsZXQgeDsgbWV0aG9kIG" _
"xhYmVsICgpIHsgcmV0dXJuICJvayI7IH0gfYCBggAB";
like(
marshal_load_message(invalid_code_dependency_blob),
/failed/,
"load rejects invalid code dependency references",
);
let malformed_code_capture_blob :=
"2dn3hmxaVVpVLU1BUlNIQUwBoIIAAIGCCIEAgYUBbm1hcnNoYWx" _
"fYmFkX2ZueBlmdW5jdGlvbiAoKSB7IHJldHVybiAxOyB9gYNjY" _
"2FwAQKA";
like(
marshal_load_message(malformed_code_capture_blob),
/Capture in code record 0 must be a/,
"load rejects malformed code capture records",
);
let arity_message := "";
try {
safe_to_dump( 1, 2 );
}
catch ( TypeException e ) {
arity_message := e{message};
}
like(
arity_message,
/expects 1 argument, got 2/,
"std/marshal functions check arity",
);
done_testing();