// Copyright (c) 2021, 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. import 'dart:async'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:test/test.dart'; void main() async { test('sync', () async { final freed = []; void freeInt(int i) { freed.add(i); } using((Arena arena) { arena.using(1234, freeInt); expect(freed.isEmpty, true); }); expect(freed, [1234]); }); test('async', () async { /// Calling [using] waits with releasing its resources until after /// [Future]s complete. final freed = []; void freeInt(int i) { freed.add(i); } final Future myFutureInt = using((Arena arena) { return Future.microtask(() { arena.using(1234, freeInt); return 1; }); }); expect(freed.isEmpty, true); await myFutureInt; expect(freed, [1234]); }); test('throw', () { /// [using] waits with releasing its resources until after [Future]s /// complete. final freed = []; void freeInt(int i) { freed.add(i); } // Resources are freed also when abnormal control flow occurs. var didThrow = false; try { using((Arena arena) { arena.using(1234, freeInt); expect(freed.isEmpty, true); throw Exception('Exception 1'); }); } on Exception { expect(freed.single, 1234); didThrow = true; } expect(didThrow, true); }); test('allocate', () { final countingAllocator = CountingAllocator(); // To ensure resources are freed, wrap them in a [using] call. using((Arena arena) { final p = arena(2); p[1] = p[0]; }, countingAllocator); expect(countingAllocator.freeCount, 1); }); test('allocate throw', () { // Resources are freed also when abnormal control flow occurs. var didThrow = false; final countingAllocator = CountingAllocator(); try { using((Arena arena) { final p = arena(2); p[0] = 25; throw Exception('Exception 2'); }, countingAllocator); } on Exception { expect(countingAllocator.freeCount, 1); didThrow = true; } expect(didThrow, true); }); test('toNativeUtf8', () { final countingAllocator = CountingAllocator(); using((Arena arena) { final p = 'Hello world!'.toNativeUtf8(allocator: arena); expect(p.toDartString(), 'Hello world!'); }, countingAllocator); expect(countingAllocator.freeCount, 1); }); test('zone', () async { final freed = []; void freeInt(int i) { freed.add(i); } withZoneArena(() { zoneArena.using(1234, freeInt); expect(freed.isEmpty, true); }); expect(freed.length, 1); expect(freed.single, 1234); }); test('zone async', () async { /// [using] waits with releasing its resources until after [Future]s /// complete. final freed = []; void freeInt(int i) { freed.add(i); } final Future myFutureInt = withZoneArena(() { return Future.microtask(() { zoneArena.using(1234, freeInt); return 1; }); }); expect(freed.isEmpty, true); await myFutureInt; expect(freed.length, 1); expect(freed.single, 1234); }); test('zone throw', () { /// [using] waits with releasing its resources until after [Future]s /// complete. final freed = []; void freeInt(int i) { freed.add(i); } // Resources are freed also when abnormal control flow occurs. var didThrow = false; try { withZoneArena(() { zoneArena.using(1234, freeInt); expect(freed.isEmpty, true); throw Exception('Exception 3'); }); } on Exception { expect(freed.single, 1234); didThrow = true; } expect(didThrow, true); expect(freed.single, 1234); }); test('zone future error', () async { var caughtError = false; var uncaughtError = false; Future asyncFunction() async { throw Exception('Exception 4'); } final future = runZonedGuarded( () { return withZoneArena(asyncFunction).catchError((error) { caughtError = true; return 5; }); }, (error, stackTrace) { uncaughtError = true; }, ); final result = (await Future.wait([future!])).single; expect(result, 5); expect(caughtError, true); expect(uncaughtError, false); }); test('allocate during releaseAll', () { final countingAllocator = CountingAllocator(); final arena = Arena(countingAllocator); arena.using(arena(), (Pointer discard) { arena(); }); expect(countingAllocator.allocationCount, 1); expect(countingAllocator.freeCount, 0); arena.releaseAll(reuse: true); expect(countingAllocator.allocationCount, 2); expect(countingAllocator.freeCount, 2); }); } /// Keeps track of the number of allocates and frees for testing purposes. class CountingAllocator implements Allocator { final Allocator wrappedAllocator; int allocationCount = 0; int freeCount = 0; CountingAllocator([this.wrappedAllocator = calloc]); @override Pointer allocate(int byteCount, {int? alignment}) { allocationCount++; return wrappedAllocator.allocate(byteCount, alignment: alignment); } @override void free(Pointer pointer) { freeCount++; return wrappedAllocator.free(pointer); } }