// Guards the vf_mandelbrot contract at the C boundary. // // Everything else in the suite goes through lib/vflutter_ffi.dart, which // always passes a correctly sized buffer. These tests deliberately bypass it // and call the raw symbol the way a careless C caller would, because the // point of `buf_len` is to survive exactly that. Needs the native build on // the loader path — see widget_test.dart. import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:vflutter_ffi/vflutter_ffi.dart' as v; typedef _MandelbrotNative = Void Function(Pointer, Size, Int32, Int32, Double, Double, Double, Int32, Int32, Int32); typedef _MandelbrotDart = void Function(Pointer, int, int, int, double, double, double, int, int, int); void main() { v.ensureInitialized(); final mandelbrot = DynamicLibrary.open('libvflutter.so') .lookupFunction<_MandelbrotNative, _MandelbrotDart>('vf_mandelbrot'); const w = 16; const h = 16; const bytes = w * h * 4; // A canary region past the band. Nothing below may touch it. const canary = 1024; const canaryByte = 0xAB; late Pointer buf; setUp(() { buf = calloc(bytes + canary); for (var i = bytes; i < bytes + canary; i++) { buf[i] = canaryByte; } }); tearDown(() => calloc.free(buf)); bool canaryIntact() { for (var i = bytes; i < bytes + canary; i++) { if (buf[i] != canaryByte) return false; } return true; } /// True if V wrote anything into the band at all. bool bandTouched() { for (var i = 0; i < bytes; i++) { if (buf[i] != 0) return true; } return false; } void expectNoOp(String why) { expect(bandTouched(), isFalse, reason: '$why: V wrote into the band'); expect(canaryIntact(), isTrue, reason: '$why: V wrote past the band'); } test('a correctly sized call fills the whole band', () { mandelbrot(buf, bytes, w, h, -0.5, 0.0, 3.0, 50, 0, h); for (var i = 3; i < bytes; i += 4) { expect(buf[i], 255, reason: 'alpha at pixel ${i ~/ 4}'); } expect(canaryIntact(), isTrue); }); test('a buf_len one byte short is refused outright', () { // The tempting bug is to fill what fits and overrun by one; this must // write nothing at all. mandelbrot(buf, bytes - 1, w, h, -0.5, 0.0, 3.0, 50, 0, h); expectNoOp('buf_len short by one'); }); test('a buf_len sized for a single row is refused', () { mandelbrot(buf, w * 4, w, h, -0.5, 0.0, 3.0, 50, 0, h); expectNoOp('buf_len sized for one row'); }); test('a zero buf_len is refused', () { mandelbrot(buf, 0, w, h, -0.5, 0.0, 3.0, 50, 0, h); expectNoOp('buf_len zero'); }); test('a null buffer is a no-op, not a crash', () { mandelbrot(nullptr, bytes, w, h, -0.5, 0.0, 3.0, 50, 0, h); expectNoOp('null buf'); }); test('an inverted band is refused', () { mandelbrot(buf, bytes, w, h, -0.5, 0.0, 3.0, 50, 10, 2); expectNoOp('y1 < y0'); }); test('geometry whose byte count overflows int32 is refused', () { // (y1 - y0) * w * 4 = 2 * 2^30 * 4 wraps to 0 in 32-bit arithmetic, which // is the case a length check alone would happily accept. mandelbrot(buf, bytes, 1 << 30, h, -0.5, 0.0, 3.0, 50, 0, 2); expectNoOp('w = 1 << 30'); mandelbrot(buf, bytes, w, h, -0.5, 0.0, 3.0, 50, 0, 0x7FFFFFFF); expectNoOp('y1 = INT32_MAX'); }); }