feat(editor): M1 pdfrx spike + pen/touch capture
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Add pdfrx 2.4.4. PenCaptureRegion routes stylus to ink (arena-bypass via PenCaptureBinding) while touch falls through to pdfrx scroll/zoom. Spike pane hosts PdfViewer with page-overlay ink at normalized coords + frame-time HUD; reachable from home screen for on-device testing. Bench/coordinate harness under integration_test. Generated assets are gitignored (regenerate via tool/gen_*.dart).
This commit is contained in:
239
tool/gen_bench_pdf.dart
Normal file
239
tool/gen_bench_pdf.dart
Normal file
@@ -0,0 +1,239 @@
|
||||
// tool/gen_bench_pdf.dart
|
||||
//
|
||||
// Generates a benchmark PDF at test/assets/large_300p.pdf using
|
||||
// package:syncfusion_flutter_pdf.
|
||||
//
|
||||
// syncfusion_flutter_pdf imports dart:ui which is only available inside the
|
||||
// Flutter SDK runtime, so this file CANNOT be run via plain `dart run`.
|
||||
//
|
||||
// USAGE:
|
||||
// flutter test tool/gen_bench_pdf.dart
|
||||
// flutter test tool/gen_bench_pdf.dart --dart-define=PAGE_COUNT=50
|
||||
//
|
||||
// The script is structured as a flutter_test file (one `test(...)` block) so
|
||||
// that `flutter test` invokes it with the full Flutter engine (dart:ui present).
|
||||
// It is NOT a real unit test — it is a code-generation tool that happens to
|
||||
// need the Flutter runtime. The test "passes" as long as the file is written
|
||||
// successfully.
|
||||
//
|
||||
// Each page contains:
|
||||
// - A bold title (page number heading)
|
||||
// - Two paragraphs of body text
|
||||
// - A ruled grid of lines (10x10)
|
||||
// - A filled rectangle and an outlined ellipse
|
||||
// This gives a realistic (non-blank) render load for pdfrx frame-timing tests.
|
||||
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' show Offset, Rect, Size;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:syncfusion_flutter_pdf/pdf.dart';
|
||||
|
||||
void main() {
|
||||
// Read PAGE_COUNT from --dart-define (default 300).
|
||||
const int pageCount = int.fromEnvironment('PAGE_COUNT', defaultValue: 300);
|
||||
|
||||
test('generate test/assets/large_300p.pdf ($pageCount pages)', () {
|
||||
final outputPath = _resolveOutputPath();
|
||||
final outFile = File(outputPath);
|
||||
outFile.parent.createSync(recursive: true);
|
||||
|
||||
final pdf = PdfDocument();
|
||||
|
||||
// Reusable fonts and brushes (created once, shared across pages).
|
||||
final titleFont = PdfStandardFont(PdfFontFamily.helvetica, 18,
|
||||
style: PdfFontStyle.bold);
|
||||
final bodyFont = PdfStandardFont(PdfFontFamily.helvetica, 10);
|
||||
final smallFont = PdfStandardFont(PdfFontFamily.helvetica, 8);
|
||||
|
||||
final blackBrush = PdfSolidBrush(PdfColor(0, 0, 0));
|
||||
final darkBlueBrush = PdfSolidBrush(PdfColor(10, 30, 80));
|
||||
final lightGrayBrush = PdfSolidBrush(PdfColor(220, 220, 220));
|
||||
final accentBrush = PdfSolidBrush(PdfColor(60, 100, 200));
|
||||
|
||||
final gridPen = PdfPen(PdfColor(180, 180, 180), width: 0.3);
|
||||
final borderPen = PdfPen(PdfColor(0, 0, 0), width: 1.0);
|
||||
final accentPen = PdfPen(PdfColor(60, 100, 200), width: 1.5);
|
||||
|
||||
final rng = Random(42); // deterministic
|
||||
|
||||
for (int i = 1; i <= pageCount; i++) {
|
||||
final page = pdf.pages.add();
|
||||
final g = page.graphics;
|
||||
final w = page.getClientSize().width;
|
||||
final h = page.getClientSize().height;
|
||||
|
||||
// ── Title ──────────────────────────────────────────────────────────────
|
||||
g.drawString(
|
||||
'BadNote Benchmark — Page $i of $pageCount',
|
||||
titleFont,
|
||||
brush: darkBlueBrush,
|
||||
bounds: Rect.fromLTWH(36, 30, w - 72, 28),
|
||||
);
|
||||
|
||||
// Horizontal rule under title
|
||||
g.drawLine(
|
||||
PdfPen(PdfColor(60, 100, 200), width: 1.0),
|
||||
Offset(36, 62),
|
||||
Offset(w - 36, 62),
|
||||
);
|
||||
|
||||
// ── Body text (two paragraphs) ────────────────────────────────────────
|
||||
final paragraph1 =
|
||||
'This page is part of a synthetic $pageCount-page benchmark PDF '
|
||||
'generated by BadNote\'s tool/gen_bench_pdf.dart. Each page carries '
|
||||
'non-trivial content (text, vector shapes, a line grid) to simulate '
|
||||
'realistic rendering load for pdfrx frame-timing measurements. '
|
||||
'Page index: $i. Seed value: ${rng.nextInt(99999)}.';
|
||||
|
||||
final paragraph2 =
|
||||
'Performance target (MUST #4, §10/M1): pdfrx fling-scroll over '
|
||||
'$pageCount pages in profile mode must stay at ≤ 16.6 ms median '
|
||||
'frame time (build + raster) and ≤ 22 ms at p95, measured over '
|
||||
'N ≥ 120 frames per §7.1 of the BadNote Phase 1 plan. If this gate '
|
||||
'fails the backend choice is invalidated. Fill: ${_lorem(rng, 60)}.';
|
||||
|
||||
g.drawString(
|
||||
paragraph1,
|
||||
bodyFont,
|
||||
brush: blackBrush,
|
||||
bounds: Rect.fromLTWH(36, 72, w - 72, 80),
|
||||
format: PdfStringFormat(lineSpacing: 4),
|
||||
);
|
||||
|
||||
g.drawString(
|
||||
paragraph2,
|
||||
bodyFont,
|
||||
brush: blackBrush,
|
||||
bounds: Rect.fromLTWH(36, 158, w - 72, 80),
|
||||
format: PdfStringFormat(lineSpacing: 4),
|
||||
);
|
||||
|
||||
// ── 10×10 ruled grid ──────────────────────────────────────────────────
|
||||
const gridLeft = 36.0;
|
||||
const gridTop = 260.0;
|
||||
final gridWidth = w - 72;
|
||||
const gridHeight = 220.0;
|
||||
const cols = 10;
|
||||
const rows = 10;
|
||||
final cellW = gridWidth / cols;
|
||||
const cellH = gridHeight / rows;
|
||||
|
||||
for (int col = 0; col <= cols; col++) {
|
||||
final x = gridLeft + col * cellW;
|
||||
g.drawLine(
|
||||
gridPen, Offset(x, gridTop), Offset(x, gridTop + gridHeight));
|
||||
}
|
||||
for (int row = 0; row <= rows; row++) {
|
||||
const y = gridTop;
|
||||
g.drawLine(gridPen, Offset(gridLeft, y + row * cellH),
|
||||
Offset(gridLeft + gridWidth, y + row * cellH));
|
||||
}
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
if ((row + col) % 3 == 0) {
|
||||
g.drawRectangle(
|
||||
brush: lightGrayBrush,
|
||||
bounds: Rect.fromLTWH(
|
||||
gridLeft + col * cellW + 0.5,
|
||||
gridTop + row * cellH + 0.5,
|
||||
cellW - 1,
|
||||
cellH - 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.drawRectangle(
|
||||
pen: borderPen,
|
||||
bounds: Rect.fromLTWH(gridLeft, gridTop, gridWidth, gridHeight),
|
||||
);
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
g.drawString(
|
||||
'R${row + 1}',
|
||||
smallFont,
|
||||
brush: blackBrush,
|
||||
bounds: Rect.fromLTWH(
|
||||
gridLeft + 2,
|
||||
gridTop + row * cellH + 2,
|
||||
cellW - 4,
|
||||
cellH - 4,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Accent shapes ──────────────────────────────────────────────────────
|
||||
const shapeTop = gridTop + gridHeight + 18;
|
||||
final rectW = 60.0 + (i % 8) * 10.0;
|
||||
|
||||
g.drawRectangle(
|
||||
pen: accentPen,
|
||||
brush: accentBrush,
|
||||
bounds: Rect.fromLTWH(36, shapeTop, rectW, 24),
|
||||
);
|
||||
g.drawString(
|
||||
'Page $i',
|
||||
smallFont,
|
||||
brush: PdfSolidBrush(PdfColor(255, 255, 255)),
|
||||
bounds: Rect.fromLTWH(40, shapeTop + 6, rectW - 8, 14),
|
||||
);
|
||||
|
||||
g.drawEllipse(
|
||||
Rect.fromLTWH(36 + rectW + 16, shapeTop, 80, 24),
|
||||
pen: accentPen,
|
||||
);
|
||||
|
||||
// ── Footer ────────────────────────────────────────────────────────────
|
||||
g.drawString(
|
||||
'BadNote bench PDF • page $i/$pageCount • tool/gen_bench_pdf.dart',
|
||||
smallFont,
|
||||
brush: PdfSolidBrush(PdfColor(140, 140, 140)),
|
||||
bounds: Rect.fromLTWH(36, h - 28, w - 72, 18),
|
||||
format: PdfStringFormat(alignment: PdfTextAlignment.center),
|
||||
);
|
||||
}
|
||||
|
||||
final bytes = pdf.saveSync();
|
||||
pdf.dispose();
|
||||
|
||||
outFile.writeAsBytesSync(bytes);
|
||||
|
||||
final sizeKb = (outFile.lengthSync() / 1024).toStringAsFixed(1);
|
||||
print('Generated: $outputPath');
|
||||
print('Pages: $pageCount');
|
||||
print('Size: ${sizeKb} KB (${outFile.lengthSync()} bytes)');
|
||||
|
||||
expect(outFile.existsSync(), isTrue);
|
||||
expect(outFile.lengthSync(), greaterThan(1024),
|
||||
reason: 'PDF must be at least 1 KB');
|
||||
}, timeout: const Timeout(Duration(minutes: 5)));
|
||||
}
|
||||
|
||||
/// Resolves test/assets/large_300p.pdf relative to this script's location.
|
||||
/// tool/gen_bench_pdf.dart → project root → test/assets/large_300p.pdf
|
||||
String _resolveOutputPath() {
|
||||
// When run via `flutter test`, the CWD is the project root.
|
||||
return 'test/assets/large_300p.pdf';
|
||||
}
|
||||
|
||||
/// Generates a deterministic Lorem-Ipsum-style filler of roughly [words] words.
|
||||
String _lorem(Random rng, int words) {
|
||||
const vocab = [
|
||||
'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur',
|
||||
'adipiscing', 'elit', 'sed', 'eiusmod', 'tempor', 'incididunt',
|
||||
'labore', 'dolore', 'magna', 'aliqua', 'enim', 'minim', 'veniam',
|
||||
'quis', 'nostrud', 'exercitation', 'ullamco', 'laboris', 'nisi',
|
||||
'aliquip', 'commodo', 'consequat', 'duis', 'aute', 'irure',
|
||||
'reprehenderit', 'voluptate', 'velit', 'esse', 'cillum', 'fugiat',
|
||||
'nulla', 'pariatur', 'excepteur', 'sint', 'occaecat', 'cupidatat',
|
||||
'proident', 'culpa', 'officia', 'deserunt', 'mollit', 'anim',
|
||||
];
|
||||
return List.generate(words, (_) => vocab[rng.nextInt(vocab.length)])
|
||||
.join(' ');
|
||||
}
|
||||
220
tool/gen_dense_strokes.dart
Normal file
220
tool/gen_dense_strokes.dart
Normal file
@@ -0,0 +1,220 @@
|
||||
// tool/gen_dense_strokes.dart
|
||||
//
|
||||
// Generates synthetic ink-stroke datasets as JSON matching InkStroke.toJson()
|
||||
// (from lib/models/ink_stroke.dart + lib/models/ink_point.dart) exactly.
|
||||
//
|
||||
// Output: test/assets/dense_strokes.json
|
||||
// Format:
|
||||
// {
|
||||
// "2000": [ ...2000 InkStroke objects... ],
|
||||
// "5000": [ ...5000 InkStroke objects... ]
|
||||
// }
|
||||
//
|
||||
// Each stroke:
|
||||
// - 8–20 InkPoint objects
|
||||
// - x/y ∈ [0,1] (normalized page space, matching InkStroke coordinate model)
|
||||
// - pressure ∈ [0.2, 1.0]
|
||||
// - tilt ∈ [0.0, 30.0] degrees
|
||||
// - pointerDeviceKind: "stylus" (surface pen benchmark)
|
||||
// - tool: "pen"
|
||||
// - color: varied from a palette of realistic ink colors
|
||||
// - strokeWidth: 1.0–4.0
|
||||
//
|
||||
// Usage:
|
||||
// dart run tool/gen_dense_strokes.dart # 2000 + 5000 (defaults)
|
||||
// dart run tool/gen_dense_strokes.dart 500 1000 # custom counts
|
||||
//
|
||||
// The counts are also the JSON keys (converted to strings).
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
void main(List<String> args) {
|
||||
final counts = args.isNotEmpty
|
||||
? args.map(int.parse).toList()
|
||||
: [2000, 5000];
|
||||
|
||||
final outputPath = _resolveOutputPath();
|
||||
File(outputPath).parent.createSync(recursive: true);
|
||||
|
||||
final rng = Random(12345); // deterministic seed for reproducibility
|
||||
final Map<String, dynamic> result = {};
|
||||
|
||||
for (final count in counts) {
|
||||
final strokes = List.generate(count, (i) => _generateStroke(rng, i));
|
||||
result['$count'] = strokes;
|
||||
print('Generated $count strokes');
|
||||
}
|
||||
|
||||
final jsonStr = const JsonEncoder.withIndent(null).convert(result);
|
||||
File(outputPath).writeAsStringSync(jsonStr);
|
||||
|
||||
final sizeKb = (File(outputPath).lengthSync() / 1024).toStringAsFixed(1);
|
||||
print('Output: $outputPath');
|
||||
print('Size: ${sizeKb} KB');
|
||||
for (final count in counts) {
|
||||
print(' "$count": ${(result[count.toString()] as List).length} strokes');
|
||||
}
|
||||
|
||||
// ── Inline round-trip sanity check ─────────────────────────────────────
|
||||
// Verify that the first stroke in the first dataset round-trips through the
|
||||
// InkStroke JSON shape without data loss (field names, enum values, types).
|
||||
_verifyRoundTrip(result[counts.first.toString()]);
|
||||
}
|
||||
|
||||
/// Generates one InkStroke as a plain Map matching InkStroke.toJson().
|
||||
///
|
||||
/// Field names and enum string values are taken directly from the generated
|
||||
/// code in:
|
||||
/// lib/models/ink_stroke.g.dart (_$$InkStrokeImplToJson)
|
||||
/// lib/models/ink_point.g.dart (_$$InkPointImplToJson)
|
||||
///
|
||||
/// InkStroke fields:
|
||||
/// id, points, tool, color, strokeWidth, createdAt, filled,
|
||||
/// textContent, fontSize
|
||||
///
|
||||
/// InkPoint fields:
|
||||
/// x, y, pressure, tilt, timestamp, pointerDeviceKind
|
||||
Map<String, dynamic> _generateStroke(Random rng, int index) {
|
||||
// Pick a random color from a set of realistic ink tones.
|
||||
// Stored as ARGB int (0xFF......) matching @Default(0xFF000000).
|
||||
final color = _pickColor(rng);
|
||||
|
||||
final strokeWidth = 1.0 + rng.nextDouble() * 3.0; // [1.0, 4.0]
|
||||
final pointCount = 8 + rng.nextInt(13); // [8, 20]
|
||||
|
||||
// Start position — random page location
|
||||
double x = 0.05 + rng.nextDouble() * 0.90; // [0.05, 0.95]
|
||||
double y = 0.05 + rng.nextDouble() * 0.90;
|
||||
|
||||
// Simulate a realistic hand-drawn stroke: incremental movement with
|
||||
// small steps (realistic velocity on a ~A4 page at ~1000 DPI effective).
|
||||
final points = <Map<String, dynamic>>[];
|
||||
int timestamp = DateTime.now().millisecondsSinceEpoch - (5000 - index * 2);
|
||||
|
||||
for (int p = 0; p < pointCount; p++) {
|
||||
// Step in a semi-consistent direction with jitter
|
||||
final angle = rng.nextDouble() * 2 * pi;
|
||||
final step = 0.005 + rng.nextDouble() * 0.015; // [0.005, 0.02] page-units
|
||||
x = (x + cos(angle) * step).clamp(0.0, 1.0);
|
||||
y = (y + sin(angle) * step).clamp(0.0, 1.0);
|
||||
|
||||
// Pressure ramps up then down (pen-press profile)
|
||||
final t = p / (pointCount - 1);
|
||||
final basePressure = sin(t * pi); // 0→1→0 over the stroke
|
||||
final pressure = (0.2 + basePressure * 0.8 + (rng.nextDouble() - 0.5) * 0.1)
|
||||
.clamp(0.2, 1.0);
|
||||
|
||||
final tilt = rng.nextDouble() * 30.0; // [0, 30] degrees
|
||||
|
||||
timestamp += 8 + rng.nextInt(8); // ~8–16 ms between points (120 Hz stylus)
|
||||
|
||||
points.add({
|
||||
'x': _round6(x),
|
||||
'y': _round6(y),
|
||||
'pressure': _round6(pressure),
|
||||
'tilt': _round6(tilt),
|
||||
'timestamp': timestamp,
|
||||
// enum string from _$InputDeviceKindEnumMap in ink_point.g.dart
|
||||
'pointerDeviceKind': 'stylus',
|
||||
});
|
||||
}
|
||||
|
||||
// createdAt as ISO-8601 string (DateTime.toIso8601String() format)
|
||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(timestamp - pointCount * 12)
|
||||
.toIso8601String();
|
||||
|
||||
return {
|
||||
'id': 'bench_${index.toString().padLeft(6, '0')}',
|
||||
'points': points,
|
||||
// enum string from _$PenToolEnumMap in ink_stroke.g.dart
|
||||
'tool': 'pen',
|
||||
'color': color,
|
||||
'strokeWidth': _round6(strokeWidth),
|
||||
'createdAt': createdAt,
|
||||
'filled': false,
|
||||
'textContent': null,
|
||||
'fontSize': 14.0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one of several realistic ink colors as an ARGB int.
|
||||
/// These match the range of values that @Default(0xFF000000) int color stores.
|
||||
int _pickColor(Random rng) {
|
||||
// Palette: black, dark-blue, dark-red, dark-green, dark-purple, charcoal
|
||||
const palette = [
|
||||
0xFF000000, // black
|
||||
0xFF0A1E50, // dark navy
|
||||
0xFF800020, // dark red
|
||||
0xFF1A4D1A, // dark green
|
||||
0xFF3D0066, // dark purple
|
||||
0xFF1C1C1C, // charcoal
|
||||
0xFF002B5C, // midnight blue
|
||||
0xFF4B0000, // deep crimson
|
||||
];
|
||||
return palette[rng.nextInt(palette.length)];
|
||||
}
|
||||
|
||||
/// Rounds a double to 6 decimal places to keep JSON compact and exact.
|
||||
double _round6(double v) => double.parse(v.toStringAsFixed(6));
|
||||
|
||||
/// Resolves test/assets/dense_strokes.json relative to this script.
|
||||
String _resolveOutputPath() {
|
||||
final scriptUri = Platform.script;
|
||||
final toolDir = File.fromUri(scriptUri).parent;
|
||||
final projectRoot = toolDir.parent;
|
||||
return '${projectRoot.path}/test/assets/dense_strokes.json';
|
||||
}
|
||||
|
||||
/// Minimal round-trip verification that confirms the JSON shape produced
|
||||
/// here matches InkStroke.fromJson() expectations.
|
||||
///
|
||||
/// We cannot call actual Dart model classes (they import Flutter packages),
|
||||
/// so we do a structural check: re-parse the JSON and assert that every
|
||||
/// required field survives the round-trip with the correct type.
|
||||
void _verifyRoundTrip(dynamic dataset) {
|
||||
final strokes = dataset as List<dynamic>;
|
||||
assert(strokes.isNotEmpty, 'Dataset must not be empty');
|
||||
|
||||
final raw = strokes.first as Map<String, dynamic>;
|
||||
|
||||
// Re-encode → decode to simulate fromJson parsing.
|
||||
final encoded = jsonEncode(raw);
|
||||
final decoded = jsonDecode(encoded) as Map<String, dynamic>;
|
||||
|
||||
// Assert required InkStroke fields exist with correct types.
|
||||
void check(String field, Type type) {
|
||||
final val = decoded[field];
|
||||
assert(
|
||||
val == null || val.runtimeType.toString().contains(type.toString()) || val is num || val is String || val is bool || val is List,
|
||||
'Field "$field" missing or wrong type: ${val.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
assert(decoded['id'] is String, 'id must be String');
|
||||
assert(decoded['points'] is List, 'points must be List');
|
||||
assert(decoded['tool'] == 'pen', 'tool enum must be "pen"');
|
||||
assert(decoded['color'] is int || decoded['color'] is num, 'color must be int/num');
|
||||
assert(decoded['strokeWidth'] is double || decoded['strokeWidth'] is num,
|
||||
'strokeWidth must be num');
|
||||
assert(decoded['createdAt'] is String, 'createdAt must be String (ISO-8601)');
|
||||
assert(decoded['filled'] is bool, 'filled must be bool');
|
||||
assert(decoded['fontSize'] is double || decoded['fontSize'] is num,
|
||||
'fontSize must be num');
|
||||
|
||||
final points = decoded['points'] as List<dynamic>;
|
||||
assert(points.isNotEmpty, 'stroke must have at least one point');
|
||||
final p0 = points.first as Map<String, dynamic>;
|
||||
|
||||
assert(p0['x'] is num, 'InkPoint.x must be num');
|
||||
assert(p0['y'] is num, 'InkPoint.y must be num');
|
||||
assert(p0['pressure'] is num, 'InkPoint.pressure must be num');
|
||||
assert(p0['tilt'] is num, 'InkPoint.tilt must be num');
|
||||
assert(p0['timestamp'] is int || p0['timestamp'] is num,
|
||||
'InkPoint.timestamp must be int/num');
|
||||
assert(p0['pointerDeviceKind'] == 'stylus',
|
||||
'pointerDeviceKind must be "stylus"');
|
||||
|
||||
print('Round-trip check: PASS (all required fields present with correct types)');
|
||||
}
|
||||
39
tool/test.sh
Executable file
39
tool/test.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# tool/test.sh — flutter test wrapper with sqlite3 workaround.
|
||||
#
|
||||
# WHY THIS EXISTS:
|
||||
# BadNote vendors sqlite3 native binaries under vendor/sqlite3/ (selected via
|
||||
# the pubspec.yaml `hooks.user_defines.sqlite3.source: test-sqlite3` block).
|
||||
# On Linux the vendored file is `vendor/sqlite3/libsqlite3.x64.linux.so`.
|
||||
# Without pointing the dynamic linker at it, `flutter test` either falls back
|
||||
# to a system sqlite3 (wrong version / missing) or tries to download one at
|
||||
# build time (blocked behind the GFW on this machine).
|
||||
#
|
||||
# Setting LD_LIBRARY_PATH to the vendor dir tells the linker to prefer the
|
||||
# vendored shared library. The Flutter toolchain here (3.41.4 / Dart 3.10.8)
|
||||
# does NOT forward proxy env vars to build hooks, so LD_LIBRARY_PATH is the
|
||||
# reliable workaround for local Linux development.
|
||||
#
|
||||
# On Windows CI the vendored sqlite3.x64.windows.dll is picked up
|
||||
# automatically by the native-asset build — no wrapper needed there.
|
||||
#
|
||||
# USAGE:
|
||||
# tool/test.sh # run all tests
|
||||
# tool/test.sh test/foo_test.dart # run a specific test file
|
||||
# tool/test.sh --coverage # pass any flutter test flags
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
VENDOR_SQLITE="${PROJECT_ROOT}/vendor/sqlite3"
|
||||
|
||||
if [ ! -d "${VENDOR_SQLITE}" ]; then
|
||||
echo "WARNING: vendor/sqlite3/ not found at ${VENDOR_SQLITE}" >&2
|
||||
echo " Proceeding without LD_LIBRARY_PATH override." >&2
|
||||
else
|
||||
export LD_LIBRARY_PATH="${VENDOR_SQLITE}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
|
||||
fi
|
||||
|
||||
exec flutter test "$@"
|
||||
Reference in New Issue
Block a user