Files
sdk/tests/ffi/run_isolate_group_run_test.dart
T
Alexander Aprelev f3bc347448 [core/shared] Allow for BigInt to work in isolategroup-bound callbacks.
Introduce runtime checks for BigInt to allow it being used as deeply immutable class.

TEST=run_isolate_group_run_test

Bug: https://github.com/dart-lang/sdk/issues/61030
Bug: https://github.com/dart-lang/sdk/issues/62601
Change-Id: I343862bdf5603be571d3bf937261dd61aca9ca0e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/477040
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
2026-03-02 13:52:34 -08:00

669 lines
15 KiB
Dart

// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Tests IsolateGroup.runSync - what works, what doesn't.
//
// VMOptions=--experimental-shared-data
// VMOptions=--experimental-shared-data --use-slow-path
// VMOptions=--experimental-shared-data --use-slow-path --stacktrace-every=100
// VMOptions=--experimental-shared-data --dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
// VMOptions=--experimental-shared-data --test_il_serialization
// VMOptions=--experimental-shared-data --profiler --profile_vm=true
// VMOptions=--experimental-shared-data --profiler --profile_vm=false
import 'package:dart_internal/isolate_group.dart' show IsolateGroup;
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import "package:expect/expect.dart";
import "run_isolate_group_run_test.dart" deferred as lib1;
main(List<String> args) {
testUpdateSharedVar();
testReturnsConstant();
testReturnsList();
testReturnsNotSharedFinal();
testUpdateSharedVarWithNoInitializer();
testFailToAccessNotSharedVarWithInitializer();
testFailToAccessNotSharedVarWithoutInitializer();
testFailToCaptureLateFinalVar();
testCapturesFinalNotSharedVar();
testCyclesBetweenClosures();
testUpdateNotSharedStaticField();
testUpdateSharedStringStaticVar();
testClosure();
testFailToAssignClosureWithListToSharedVar();
testCapturesClassWithDeeplyImmutableClosure();
testFailToCaptureClassWithNonFinalCapturingClosure();
testCapturesClassWithFinalCapturingClosure();
testPrint();
testFailToIsolateGroupRunSyncThrows();
testIsolateCurrent();
testFailToIsolateExit();
testFailToIsolateSpawn();
testStringMethodTearoff();
testListMethodTearoff(args);
testFailToReceivePort();
testFailToDeferredLibrary();
testFailToEnvironment();
testUserTag();
testDoubleToString();
testBase64Decoder();
testRandom();
testEncoding();
testRecursiveToString();
testBytesBuilder();
testRegExp();
testContentType();
testBigInt();
print("All tests completed :)");
}
///
@pragma('vm:shared')
var list_length = 0;
void testUpdateSharedVar() {
IsolateGroup.runSync(() {
final l = <int>[];
for (int i = 0; i < 100; i++) {
l.add(i);
}
list_length = l.length;
});
Expect.equals(100, list_length);
}
///
@pragma('vm:shared')
final foo_final = 1234;
void testReturnsNotSharedFinal() {
Expect.equals(1234, IsolateGroup.runSync(() => foo_final));
}
///
void testReturnsConstant() {
Expect.equals(42, IsolateGroup.runSync(() => 42));
}
///
void testReturnsList() {
Expect.listEquals([1, 2, 3], IsolateGroup.runSync(() => [1, 2, 3]));
}
///
var foo_no_initializer;
@pragma('vm:never-inline')
updateFooNoInitializer() {
foo_no_initializer = 78;
}
void testFailToAccessNotSharedVarWithoutInitializer() {
updateFooNoInitializer();
Expect.throws(
() {
IsolateGroup.runSync(() {
return foo_no_initializer;
});
},
(e) => e is Error && e.toString().contains("AccessError"),
'Expect error accessing',
);
Expect.throws(
() {
IsolateGroup.runSync(() {
foo_no_initializer = 456;
});
},
(e) => e is Error && e.toString().contains("AccessError"),
'Expect error accessing',
);
Expect.equals(78, foo_no_initializer);
}
///
void testFailToCaptureLateFinalVar() {
late final int late_final_var;
late_final_var = 12;
Expect.throws(() {
IsolateGroup.runSync(() {
return late_final_var;
});
}, (e) => e is Error && e.toString().contains("Only final"));
}
///
@pragma('vm:never-inline')
calculateTwelve() => 12;
void testCapturesFinalNotSharedVar() {
final int late_final_var = calculateTwelve();
Expect.equals(late_final_var, IsolateGroup.runSync(() => late_final_var));
}
///
void testCyclesBetweenClosures() {
final int i = 0;
final void Function() func1 = () {
if (i > 0) {
throw i;
}
};
void func2() {
func1();
}
;
IsolateGroup.runSync(func2);
}
///
var foo = 42;
@pragma('vm:never-inline')
updateFoo() {
foo = 56;
}
void testFailToAccessNotSharedVarWithInitializer() {
updateFoo();
Expect.throws(
() {
IsolateGroup.runSync(() {
return foo;
});
},
(e) => e is Error && e.toString().contains("AccessError"),
'Expect error accessing',
);
Expect.throws(
() {
IsolateGroup.runSync(() {
foo = 123;
});
},
(e) => e is Error && e.toString().contains("AccessError"),
'Expect error accessing',
);
Expect.equals(56, foo);
}
///
@pragma('vm:shared')
var shared_foo_no_initializer;
void testUpdateSharedVarWithNoInitializer() {
IsolateGroup.runSync(() {
shared_foo_no_initializer = 2345;
});
Expect.equals(2345, IsolateGroup.runSync(() => shared_foo_no_initializer));
}
///
class Baz {
static late final foo;
}
@pragma('vm:never-inline')
updateBazFoo() {
Baz.foo = 42;
}
void testUpdateNotSharedStaticField() {
updateBazFoo();
Expect.equals(42, Baz.foo);
}
///
@pragma('vm:shared')
String string_foo = "";
void testUpdateSharedStringStaticVar() {
IsolateGroup.runSync(() {
string_foo = "foo bar";
});
Expect.equals("foo bar", string_foo);
}
///
@pragma('vm:never-inline')
@pragma('vm:shared')
var closure = () {
return 42;
};
void testClosure() {
final result = IsolateGroup.runSync(() {
return closure();
});
Expect.equals(42, result);
}
///
@pragma('vm:shared')
dynamic getList;
void testFailToAssignClosureWithListToSharedVar() {
final list = <String>[];
Expect.throws(
() {
getList = () => list;
},
(e) => e is Error && e.toString().contains("Only trivially-immutable"),
"Expect error assigning list to shared variable",
);
print(getList);
}
///
@pragma('vm:deeply-immutable')
final class FooWithFunc {
final int Function() func;
final int kuka;
FooWithFunc(this.func, this.kuka);
}
void testCapturesClassWithDeeplyImmutableClosure() {
final foo = FooWithFunc(() => 42, 43);
final result = IsolateGroup.runSync(() {
return foo.func();
});
Expect.equals(42, result);
}
@pragma('vm:shared')
bool skipAccessAtRuntime = false;
void testFailToCaptureClassWithNonFinalCapturingClosure() {
var closureValue = 42;
// Construction of this "deeply-immutable" object should actually fail at
// runtime due to closure closing over non-final [closureValue] variable.
Expect.throws(
() => FooWithFunc(() => skipAccessAtRuntime ? -1 : closureValue, 43),
(e) => e is Error && e.toString().contains("Only final not-late"),
);
}
void testCapturesClassWithFinalCapturingClosure() {
final finalClosureValue = 43;
final foo = FooWithFunc(
() => skipAccessAtRuntime ? -1 : finalClosureValue,
44,
);
skipAccessAtRuntime = true;
Expect.equals(
-1,
IsolateGroup.runSync(() {
return foo.func();
}),
);
skipAccessAtRuntime = false;
Expect.equals(
43,
IsolateGroup.runSync(() {
return foo.func();
}),
);
}
///
void testPrint() {
IsolateGroup.runSync(() {
print('42');
});
}
///
void testFailToIsolateGroupRunSyncThrows() {
Expect.throws(
() {
IsolateGroup.runSync(() {
throw "error";
});
},
(e) => e == "error",
'Expect thrown error',
);
}
///
void testIsolateCurrent() {
Expect.notEquals(() {
IsolateGroup.runSync(() {
return Isolate.current;
});
}, Isolate.current);
}
///
void testFailToIsolateExit() {
Expect.throws(() {
IsolateGroup.runSync(() {
Isolate.exit();
});
}, (e) => e.toString().contains("Attempt to access isolate static field"));
}
///
void testFailToIsolateSpawn() {
Expect.throws(() {
IsolateGroup.runSync(() {
Isolate.spawn((_) {}, null);
});
}, (e) => e.toString().contains("Attempt to access isolate static field"));
Expect.throws(() {
IsolateGroup.runSync(() {
Isolate.spawnUri(Uri.parse("http://127.0.0.1"), [], (_) {});
});
}, (e) => e.toString().contains("Attempt to access isolate static field"));
}
///
testStringMethodTearoff() {
@pragma('vm:shared')
final stringTearoff = "abc".toString;
IsolateGroup.runSync(() {
stringTearoff;
});
}
///
testListMethodTearoff(List<String> args) {
final listTearoff = args.insert;
Expect.throws(
() {
IsolateGroup.runSync(() {
listTearoff;
});
},
(e) =>
e is ArgumentError && e.toString().contains("Only trivially-immutable"),
);
}
///
@pragma('vm:shared')
SendPort? sp;
void testFailToReceivePort() {
Expect.throws(
() {
IsolateGroup.runSync(() {
ReceivePort();
});
},
(e) =>
e is ArgumentError &&
e.toString().contains("Only available when running in context"),
);
final rp = ReceivePort();
Expect.throws(
() {
IsolateGroup.runSync(() {
sp = rp.sendPort;
});
},
(e) =>
e is ArgumentError && e.toString().contains("Only trivially-immutable"),
);
rp.close();
}
///
thefun() {}
void testFailToDeferredLibrary() {
Expect.throws(() {
IsolateGroup.runSync(() {
lib1.thefun();
});
}, (e) => e is ArgumentError && e.toString().contains("Only available when"));
}
///
void testFailToEnvironment() {
Expect.throws(() {
IsolateGroup.runSync(() {
new bool.hasEnvironment("Anything");
});
}, (e) => e is ArgumentError && e.toString().contains("Only available when"));
}
///
@pragma('vm:shared')
String default_tag = "";
void testUserTag() {
IsolateGroup.runSync(() {
default_tag = UserTag.defaultTag.toString();
});
Expect.notEquals("", default_tag);
}
///
@pragma('vm:shared')
double pi = 3.14159;
void testDoubleToString() {
final result = IsolateGroup.runSync(() {
return pi.toString();
});
Expect.equals("3.14159", result);
final resultIdentical = IsolateGroup.runSync(() {
return identical(pi.toString(), pi.toString());
});
Expect.isTrue(resultIdentical);
}
///
void testBase64Decoder() {
Expect.listEquals(
"abcdefghijklmnopqrstuvwxyz".codeUnits,
IsolateGroup.runSync(() {
return Base64Decoder().convert("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=");
}),
);
}
///
void testRandom() {
IsolateGroup.runSync(() {
Random().nextInt(10);
});
}
///
void testEncoding() {
Expect.listEquals(
[0x31, 0x32, 0x33, 0x61, 0x62, 0x63],
IsolateGroup.runSync(
() => Encoding.getByName("us-ascii")!.encode("123abc"),
),
);
Expect.identical(
ascii,
IsolateGroup.runSync(() => Encoding.getByName("us-ascii")),
);
Expect.identical(
utf8,
IsolateGroup.runSync(() => Encoding.getByName("utf-8")),
);
}
///
void testRecursiveToString() {
Expect.equals(
"[foo, bar, [...], baz]",
IsolateGroup.runSync(() {
var l = <Object>["foo", "bar"];
l.add(l);
l.add("baz");
return l.toString();
}),
);
}
///
void testBytesBuilder() {
IsolateGroup.runSync(() {
BytesBuilder builder = new BytesBuilder();
builder.add([1, 2, 3]);
Expect.listEquals([1, 2, 3], builder.toBytes());
builder.clear();
Expect.isTrue(builder.isEmpty);
});
}
///
void testRegExp() {
IsolateGroup.runSync(() {
RegExp re = new RegExp("abc", multiLine: false, caseSensitive: true);
Match? m = re.firstMatch("defabcghi");
Expect.equals("abc", m?[0]);
});
}
///
void testContentType() {
IsolateGroup.runSync(() {
Expect.isNotNull(ContentType.text);
Expect.equals("text/plain; charset=utf-8", ContentType.text.toString());
Expect.isNotNull(ContentType.html);
Expect.equals("text/html; charset=utf-8", ContentType.html.toString());
Expect.isNotNull(ContentType.json);
Expect.equals(
"application/json; charset=utf-8",
ContentType.json.toString(),
);
});
}
///
expectSum(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a + b;
Expect.equals(expected, actual);
}
expectDifference(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a - b;
Expect.equals(expected, actual);
}
expectQuotient(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a ~/ b;
Expect.equals(expected, actual);
}
expectRemainder(aString, bString, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt b = BigInt.parse(bString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a % b;
Expect.equals(expected, actual);
}
expectShifted(aString, n, expectedString) {
BigInt a = BigInt.parse(aString, radix: 16);
BigInt expected = BigInt.parse(expectedString, radix: 16);
BigInt actual = a << n;
Expect.equals(expected, actual);
}
void testBigInt() {
IsolateGroup.runSync(() {
Expect.equals("${BigInt.zero}", "0");
Expect.equals("${BigInt.one}", "1");
Expect.equals("${BigInt.two}", "2");
expectSum(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"1ad478de9e340aba6cb25ae8dbb531d2bc0105fa0",
);
expectSum(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"1ad478de9e340aba6cb25ae8dbb531d2bc0105fa0",
);
expectDifference(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"3b04b6a8ac2e74f9845c182e303993e0efa8184",
);
expectDifference(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"-3b04b6a8ac2e74f9845c182e303993e0efa8184",
);
expectRemainder(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"3b04b6a8ac2e74f9845c182e303993e0efa8184",
);
expectRemainder(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
);
expectQuotient(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"1",
);
expectQuotient(
"d4cba13fac3ee22b996ff6856c27c1f6d88aef0e",
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
"0",
);
expectShifted(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
0,
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
);
expectShifted(
"d87becaa3701c97b31b5b8084f2b5b34e7857092",
1,
"1b0f7d9546e0392f6636b70109e56b669cf0ae124",
);
});
}