Tests for more platforms (#879)

This commit is contained in:
Simon Leier
2022-11-12 19:23:41 +01:00
committed by GitHub
parent c23aada193
commit 01da127a7c
56 changed files with 761 additions and 474 deletions
+22
View File
@@ -0,0 +1,22 @@
name: "Prepare Build"
description: "Prepares the build for Isar Core"
runs:
using: "composite"
steps:
- name: Install LLVM and Clang
if: runner.os == 'Windows'
uses: KyleMayes/install-llvm-action@v1
with:
version: "11.0"
directory: ${{ runner.temp }}/llvm
- name: Set LIBCLANG_PATH
if: runner.os == 'Windows'
shell: bash
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
# See https://github.com/godot-rust/godot-rust/pull/920
- name: "Workaround Android NDK due to Rust bug"
if: runner.os == 'Linux' || runner.os == 'macOS'
shell: bash
run: >
find -L $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION -name libunwind.a
-execdir sh -c 'echo "INPUT(-lunwind)" > libgcc.a' \;
+1 -1
View File
@@ -3,7 +3,7 @@ name: Docs
on:
push:
branches:
- main
- main
permissions:
contents: read
+22 -35
View File
@@ -3,20 +3,20 @@ name: Isar release
on:
push:
tags:
- '*'
- "*"
jobs:
verify_version:
name: Verify version matches release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dart-lang/setup-dart@v1
- name: Verify release version
run: |
dart pub get
dart tool/verify_release_version.dart ${{ github.ref_name }}
working-directory: packages/isar
- uses: actions/checkout@v3
- uses: dart-lang/setup-dart@v1
- name: Verify release version
run: |
dart pub get
dart tool/verify_release_version.dart ${{ github.ref_name }}
working-directory: packages/isar
build_binaries:
name: Build Binaries
@@ -54,33 +54,20 @@ jobs:
script: build_windows.sh x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v1
if: matrix.os == 'windows-latest'
with:
version: "11.0"
directory: ${{ runner.temp }}/llvm
- name: Set LIBCLANG_PATH
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
if: matrix.os == 'windows-latest'
# See https://github.com/godot-rust/godot-rust/pull/920
- name: "Workaround Android NDK due to Rust bug"
if: matrix.os == 'ubuntu-latest'
run: >
find -L $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION -name libunwind.a
-execdir sh -c 'echo "INPUT(-lunwind)" > libgcc.a' \;
- name: Set env
run: echo "ISAR_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
- name: Build binary
run: bash tool/${{ matrix.script }}
- name: Upload binary
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ matrix.artifact_name }}
asset_name: ${{ matrix.artifact_name }}
tag: ${{ github.ref }}
- uses: actions/checkout@v3
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Set env
run: echo "ISAR_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
- name: Build binary
run: bash tool/${{ matrix.script }}
- name: Upload binary
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ matrix.artifact_name }}
asset_name: ${{ matrix.artifact_name }}
tag: ${{ github.ref }}
build_inspector:
name: Build Inspector
+181 -71
View File
@@ -3,10 +3,10 @@ name: Dart CI
on:
push:
branches:
- main
- main
pull_request:
branches:
- main
- main
jobs:
format:
@@ -48,35 +48,65 @@ jobs:
run: flutter analyze
test:
name: Dart Test
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- run: echo "$OSTYPE"
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Unit tests
run: flutter test -j 1
working-directory: packages/isar_test
valgrind:
name: Valgrind
runs-on: ubuntu-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Install valgrind and llvm
run: sudo apt update && sudo apt install -y valgrind libclang-dev
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Valgrind
run: |
flutter pub get
dart tool/generate_long_double_test.dart
dart tool/generate_prepare_test.dart
flutter pub run build_runner build
flutter test -j 1
dart compile exe integration_test/all_tests.dart
valgrind \
--leak-check=full \
--error-exitcode=1 \
--show-mismatched-frees=no \
--show-possibly-lost=no \
--errors-for-leak-kinds=definite \
integration_test/all_tests.exe
working-directory: packages/isar_test
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Unit tests
run: |
flutter pub get
dart tool/generate_long_double_test.dart
dart tool/generate_prepare_test.dart
flutter pub run build_runner build
working-directory: packages/isar_test
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Add packages
run: |
flutter pub add json_annotation
@@ -101,6 +131,7 @@ jobs:
files: packages/isar/lcov_isar_test.info
test_generator:
name: Generator Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -111,99 +142,178 @@ jobs:
dart test
working-directory: packages/isar_generator
drive_ios:
strategy:
matrix:
device:
- "iPhone 8"
- "iPhone 11 Pro Max"
fail-fast: false
integration_test_ios:
name: Integration Test iOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Start simulator
uses: futureware-tech/simulator-action@v2
with:
model: ${{ matrix.device }}
model: iPhone 13
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_ios.sh
unzip isar_ios.xcframework.zip -d packages/isar_flutter_libs/ios
- name: Run Flutter Driver tests
run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Integration tests
run: flutter test integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
drive_android:
integration_test_android:
name: Integration Test Android
runs-on: macos-latest
if: ${{ false }}
strategy:
matrix:
api-level: [29] #[21, 29]
target: [default]
fail-fast: false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'zulu'
java-version: "11"
distribution: "zulu"
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Setup old NDK
run: |
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;21.4.7075529"
ln -sfn $ANDROID_HOME/ndk/21.4.7075529 $ANDROID_HOME/ndk-bundle
- name: Update ANDROID_NDK_HOME
run: echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/21.4.7075529" >> $GITHUB_ENV
- name: Build Isar Core
run: |
bash tool/build_android.sh x64
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/x86_64
mv libisar_android_x64.so packages/isar_flutter_libs/android/src/main/jniLibs/x86_64/libisar.so
- name: Run isar_generator
run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
working-directory: packages/isar_test
- name: Run Flutter Driver tests
uses: reactivecircus/android-emulator-runner@v2
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Integration tests
continue-on-error: true
timeout-minutes: ${{ inputs.timeout_minutes }}
uses: Wandalen/wretry.action@v1.0.25
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
working-directory: packages/isar_test
script: "flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart"
action: reactivecircus/android-emulator-runner@v2
with: |
api-level: 29
arch: x86_64
profile: pixel
working-directory: packages/isar_test
script: flutter test integration_test/integration_test.dart --dart-define STRESS=true
drive_desktop:
strategy:
matrix:
os: [macos-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
firebase_testlab_android:
name: Firebase Testlab Android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Install Linux requirements
run: sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev
if: matrix.os == 'ubuntu-latest'
- name: Build Isar Core macOS
if: matrix.os == 'macos-latest'
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core arm64
run: |
bash tool/build_android.sh arm64
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/arm64-v8a
mv libisar_android_arm64.so packages/isar_flutter_libs/android/src/main/jniLibs/arm64-v8a/libisar.so
- name: Build Isar Core armv7
run: |
bash tool/build_android.sh armv7
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/armeabi-v7a
mv libisar_android_armv7.so packages/isar_flutter_libs/android/src/main/jniLibs/armeabi-v7a/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Build dummy APK
run: flutter build apk integration_test/integration_test.dart
working-directory: packages/isar_test
- name: Build APKs
run: |
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget=integration_test/integration_test.dart
working-directory: packages/isar_test/android
- name: Login to Google Cloud
uses: "google-github-actions/auth@v1"
with:
credentials_json: "${{ secrets.FIREBASE_JSON }}"
- name: Run tests
run: |
gcloud firebase test android run \
--project honeyy \
--type instrumentation \
--device model=hammerhead,version=23 \
--device model=starqlteue,version=26 \
--device model=cheetah,version=33 \
--device model=1610,version=23 \
--device model=griffin,version=24 \
--device model=harpia,version=23 \
--device model=sailfish,version=25 \
--app build/app/outputs/apk/debug/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk\
working-directory: packages/isar_test
integration_test_macos:
name: Integration Test macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_macos.sh
install_name_tool -id @rpath/libisar.dylib libisar_macos.dylib
mv libisar_macos.dylib packages/isar_flutter_libs/macos/libisar.dylib
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter config --no-enable-web
flutter config --enable-macos-desktop
flutter test -d macos integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_linux:
name: Integration Test Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Install Linux requirements
run: sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev
- name: Setup headless display
uses: pyvista/setup-headless-display-action@v1
- name: Build Isar Core
run: |
bash tool/build_linux.sh x64
mv libisar_linux_x64.so packages/isar_flutter_libs/linux/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-linux-desktop
flutter test -d linux integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_windows:
name: Integration Test Windows
runs-on: windows-2019
if: ${{ false }}
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_windows.sh x64
mv isar_windows_x64.dll packages/isar_flutter_libs/windows/libisar.dll
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-windows-desktop
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart
flutter test -d windows integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
drive_chrome:
@@ -220,8 +330,8 @@ jobs:
run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter pub run build_runner build
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart -d web-server --browser-name chrome
working-directory: packages/isar_test
+8 -7
View File
@@ -18,13 +18,14 @@ final isar = await Isar.open([RecipeSchema]);
You can use the default config or provide some of the following parameters:
| Config | Description |
| -------| -------------|
| `name` | Open multiple instances with distinct names. By default, `"default"` is used. |
| `directory` | The storage location for this instance. By default, `NSDocumentDirectory` is used for iOS and `getDataDirectory` for Android. Not required for web. |
| `relaxedDurability` | Relaxes the durability guarantee to increase write performance. In case of a system crash (not app crash), it is possible to lose the last committed transaction. Corruption is not possible |
| `compactOnLaunch` | Conditions to check whether the database should be compacted when the instance is opened. |
| `inspector` | Enabled the Inspector for debug builds. For profile and release builds this option is ignored. |
| Config | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Open multiple instances with distinct names. By default, `"default"` is used. |
| `directory` | The storage location for this instance. By default, `NSDocumentDirectory` is used for iOS and `getDataDirectory` for Android. Not required for web. |
| `maxSizeMib` | The maximum size of the database file in MiB. Isar uses virtual memory which is not an endless resource so be mindful with the value here. If you open multiple instances they share the available virtual memory so each instance should have a smaller `maxSizeMib` . The default is 2048. |
| `relaxedDurability` | Relaxes the durability guarantee to increase write performance. In case of a system crash (not app crash), it is possible to lose the last committed transaction. Corruption is not possible |
| `compactOnLaunch` | Conditions to check whether the database should be compacted when the instance is opened. |
| `inspector` | Enabled the Inspector for debug builds. For profile and release builds this option is ignored. |
If an instance is already open, calling `Isar.open()` will yield the existing instance regardless of the specified parameters. That's useful for using Isar in an isolate.
+19 -19
View File
@@ -39,15 +39,15 @@ class Shoe {
Depending on the type of field, there are different conditions available.
| Condition | Description |
| ----------| ------------|
| `.equalTo(value)` | Matches values that are equal to the specified `value`. |
| `.between(lower, upper)` | Matches values that are between `lower` and `upper`. |
| `.greaterThan(bound)` | Matches values that are greater than `bound`. |
| `.lessThan(bound)` | Matches values that are less than `bound`. `null` values will be included by default because `null` is considered smaller than any other value. |
| `.isNull()` | Matches values that are `null`.|
| `.isNotNull()` | Matches values that are not `null`.|
| `.length()` | List, String and link length queries filter objects based on the number of elements in a list or link. |
| Condition | Description |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `.equalTo(value)` | Matches values that are equal to the specified `value`. |
| `.between(lower, upper)` | Matches values that are between `lower` and `upper`. |
| `.greaterThan(bound)` | Matches values that are greater than `bound`. |
| `.lessThan(bound)` | Matches values that are less than `bound`. `null` values will be included by default because `null` is considered smaller than any other value. |
| `.isNull()` | Matches values that are `null`. |
| `.isNotNull()` | Matches values that are not `null`. |
| `.length()` | List, String and link length queries filter objects based on the number of elements in a list or link. |
Let's assume the database contains four shoes with sizes 39, 40, 46 and one with an un-set (`null`) size. Unless you perform sorting, the values will be returned sorted by id.
@@ -71,13 +71,13 @@ isar.shoes.filter()
You can composite predicates using the following logical operators:
| Operator | Description |
| ---------- | ----------- |
| Operator | Description |
| ---------- | ------------------------------------------------------------------------------------ |
| `.and()` | Evaluates to `true` if both left-hand and right-hand expressions evaluate to `true`. |
| `.or()` | Evaluates to `true` if either expression evaluates to `true`. |
| `.xor()` | Evaluates to `true` if exactly one expression evaluates to `true`. |
| `.not()` | Negates the result of the following expression. |
| `.group()` | Group conditions and allow to specify order of evaluation. |
| `.or()` | Evaluates to `true` if either expression evaluates to `true`. |
| `.xor()` | Evaluates to `true` if exactly one expression evaluates to `true`. |
| `.not()` | Negates the result of the following expression. |
| `.group()` | Group conditions and allow to specify order of evaluation. |
If you want to find all shoes in size 46, you can use the following query:
@@ -152,11 +152,11 @@ A [wildcard string expression](https://en.wikipedia.org/wiki/Wildcard_character)
Sometimes it is necessary to build a query based on some conditions or for different values. Isar has a very powerful tool for building conditional queries:
| Modifier | Description |
| --------------------- | ---------------------------------------------------- |
| Modifier | Description |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `.optional(cond, qb)` | Extends the query only if the `condition` is `true`. This can be used almost anywhere in a query for example to conditionally sort or limit it. |
| `.anyOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **or**. |
| `.allOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **and**. |
| `.anyOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **or**. |
| `.allOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **and**. |
In this example, we build a method that can find shoes with an optional filter:
+4 -2
View File
@@ -4,14 +4,16 @@ import 'dart:async';
import 'package:dio/dio.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pub_app/models/asset.dart';
import 'package:pub_app/models/package.dart';
import 'package:pub_app/package_manager.dart';
import 'package:pub_app/repository.dart';
import 'package:riverpod/riverpod.dart';
final isarPod = FutureProvider((ref) {
return Isar.open([PackageSchema, AssetSchema]);
final isarPod = FutureProvider((ref) async {
final dir = await getApplicationDocumentsDirectory();
return Isar.open([PackageSchema, AssetSchema], directory: dir.path);
});
final repositoryPod = Provider((ref) {
+1 -1
View File
@@ -48,7 +48,7 @@ class PubAppBar extends ConsumerWidget implements PreferredSizeWidget {
darkMode ? Icons.light_mode_rounded : Icons.dark_mode_rounded,
),
onPressed: () {
ref.read(darkModePod.state).state = !darkMode;
ref.read(darkModePod.notifier).state = !darkMode;
},
),
],
+5
View File
@@ -3,13 +3,18 @@
### Enhancements
- Improved performance for all operations
- Added `maxSizeMiB` option to `Isar.open()` to specify the maximum size of the database file
- Significantly reduced native library size
- With the help of the community, the docs have been translated to a range of languages
- Improved API docs
- Added integration tests for more platforms to ensure high quality releases
### Fixes
- Fixed a native port that was not closed correctly in some cases
- Added swift version to podspec
- Fixed crash on Windows
- Fixed "IndexNotFound" error
## 3.0.2
+1 -1
View File
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2022 Simon Leier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+7 -1
View File
@@ -29,8 +29,10 @@ abstract class Isar {
/// The default Isar instance name.
static const String defaultName = 'default';
/// The default max Isar size.
static const int defaultMaxSizeMiB = 2048;
/// Placeholder for an auto-increment id.
// ignore: prefer_const_declarations
static const Id autoIncrement = isarAutoIncrementId;
static final Map<String, Isar> _instances = <String, Isar>{};
@@ -87,6 +89,7 @@ abstract class Isar {
List<CollectionSchema<dynamic>> schemas, {
String? directory,
String name = defaultName,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
bool relaxedDurability = true,
CompactCondition? compactOnLaunch,
bool inspector = true,
@@ -105,6 +108,7 @@ abstract class Isar {
schemas: schemas,
directory: directory,
name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch,
);
@@ -115,6 +119,7 @@ abstract class Isar {
List<CollectionSchema<dynamic>> schemas, {
String? directory,
String name = defaultName,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
bool relaxedDurability = true,
CompactCondition? compactOnLaunch,
bool inspector = true,
@@ -133,6 +138,7 @@ abstract class Isar {
schemas: schemas,
directory: directory,
name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch,
);
+137 -57
View File
@@ -3,7 +3,11 @@ part of isar;
/// Normal keys consist of a single object, composite keys multiple.
typedef IndexKey = List<Object?>;
/// Collections are used to store and retrieve your objects from Isar.
/// Use `IsarCollection` instances to find, query, and create new objects of a
/// given type in Isar.
///
/// You can get an instance of `IsarCollection` by calling `isar.get<OBJ>()` or
/// by using the generated `isar.yourCollections` getter.
abstract class IsarCollection<OBJ> {
/// The corresponding Isar instance.
Isar get isar;
@@ -14,150 +18,227 @@ abstract class IsarCollection<OBJ> {
/// The name of the collection.
String get name => schema.name;
/// {@template col_get}
/// Get a single object by its [id] or `null` if the object does not exist.
/// {@endtemplate}
Future<OBJ?> get(Id id) {
return getAll([id]).then((List<OBJ?> objects) => objects[0]);
}
/// Get a single object by its [id] or `null` if the object does not exist.
/// {@macro col_get}
OBJ? getSync(Id id) {
return getAllSync([id])[0];
}
/// {@template col_get_all}
/// Get a list of objects by their [ids] or `null` if an object does not
/// exist.
/// {@endtemplate}
Future<List<OBJ?>> getAll(List<Id> ids);
/// Get a list of objects by their [ids] or `null` if an object does not
/// exist.
/// {@macro col_get_all}
List<OBJ?> getAllSync(List<Id> ids);
/// @nodoc
@protected
/// {@template col_get_by_index}
/// Get a single object by the unique index [indexName] and [key].
///
/// Returns `null` if the object does not exist.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<OBJ?> getByIndex(String indexName, IndexKey key) {
return getAllByIndex(indexName, [key])
.then((List<OBJ?> objects) => objects[0]);
}
/// @nodoc
@protected
/// {@macro col_get_by_index}
@experimental
OBJ? getByIndexSync(String indexName, IndexKey key) {
return getAllByIndexSync(indexName, [key])[0];
}
/// @nodoc
@protected
/// {@template col_get_all_by_index}
/// Get a list of objects by the unique index [indexName] and [keys].
///
/// Returns `null` if the object does not exist.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<List<OBJ?>> getAllByIndex(String indexName, List<IndexKey> keys);
/// @nodoc
@protected
/// {@macro col_get_all_by_index}'
@experimental
List<OBJ?> getAllByIndexSync(String indexName, List<IndexKey> keys);
/// Insert or update an [object] and returns the assigned id.
/// {@template col_put}
/// Insert or update an [object]. Returns the id of the new or updated object.
///
/// If the object has an non-final id property, it will be set to the assigned
/// id. Otherwise you should use the returned id to update the object.
/// {@endtemplate}
Future<Id> put(OBJ object) {
return putAll([object]).then((List<Id> ids) => ids[0]);
}
/// Insert or update an [object] and returns the assigned id.
/// {@macro col_put}
Id putSync(OBJ object, {bool saveLinks = true}) {
return putAllSync([object], saveLinks: saveLinks)[0];
}
/// Insert or update a list of [objects] and returns the list of assigned ids.
/// {@template col_put_all}
/// Insert or update a list of [objects]. Returns the list of ids of the new
/// or updated objects.
///
/// If the objects have an non-final id property, it will be set to the
/// assigned id. Otherwise you should use the returned ids to update the
/// objects.
/// {@endtemplate}
Future<List<Id>> putAll(List<OBJ> objects);
/// Insert or update a list of [objects] and returns the list of assigned ids.
/// {@macro col_put_all}
List<Id> putAllSync(List<OBJ> objects, {bool saveLinks = true});
/// @nodoc
@protected
/// {@template col_put_by_index}
/// Insert or update the [object] by the unique index [indexName]. Returns the
/// id of the new or updated object.
///
/// If there is already an object with the same index key, it will be
/// updated and all links will be preserved. Otherwise a new object will be
/// inserted.
///
/// If the object has an non-final id property, it will be set to the assigned
/// id. Otherwise you should use the returned id to update the object.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<Id> putByIndex(String indexName, OBJ object) {
return putAllByIndex(indexName, [object]).then((List<Id> ids) => ids[0]);
}
/// @nodoc
@protected
/// {@macro col_put_by_index}
@experimental
Id putByIndexSync(String indexName, OBJ object, {bool saveLinks = true}) {
return putAllByIndexSync(indexName, [object])[0];
}
/// @nodoc
@protected
/// {@template col_put_all_by_index}
/// Insert or update a list of [objects] by the unique index [indexName].
/// Returns the list of ids of the new or updated objects.
///
/// If there is already an object with the same index key, it will be
/// updated and all links will be preserved. Otherwise a new object will be
/// inserted.
///
/// If the objects have an non-final id property, it will be set to the
/// assigned id. Otherwise you should use the returned ids to update the
/// objects.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<List<Id>> putAllByIndex(String indexName, List<OBJ> objects);
/// @nodoc
@protected
/// {@macro col_put_all_by_index}
@experimental
List<Id> putAllByIndexSync(
String indexName,
List<OBJ> objects, {
bool saveLinks = true,
});
/// {@template col_delete}
/// Delete a single object by its [id].
///
/// Returns whether the object has been deleted. Isar web always returns
/// `true`.
/// {@endtemplate}
Future<bool> delete(Id id) {
return deleteAll([id]).then((int count) => count == 1);
}
/// Delete a single object by its [id].
///
/// Returns whether the object has been deleted.
/// {@macro col_delete}
bool deleteSync(Id id) {
return deleteAllSync([id]) == 1;
}
/// {@template col_delete_all}
/// Delete a list of objects by their [ids].
///
/// Returns the number of objects that have been deleted. Isar web always
/// returns `ids.length`.
/// {@endtemplate}
Future<int> deleteAll(List<Id> ids);
/// Delete a list of objects by their [ids].
///
/// Returns the number of objects that have been deleted.
/// {@macro col_delete_all}
int deleteAllSync(List<Id> ids);
/// @nodoc
@protected
/// {@template col_delete_by_index}
/// Delete a single object by the unique index [indexName] and [key].
///
/// Returns whether the object has been deleted. Isar web always returns
/// `true`.
/// {@endtemplate}
@experimental
Future<bool> deleteByIndex(String indexName, IndexKey key) {
return deleteAllByIndex(indexName, [key]).then((int count) => count == 1);
}
/// @nodoc
@protected
/// {@macro col_delete_by_index}
@experimental
bool deleteByIndexSync(String indexName, IndexKey key) {
return deleteAllByIndexSync(indexName, [key]) == 1;
}
/// @nodoc
@protected
/// {@template col_delete_all_by_index}
/// Delete a list of objects by the unique index [indexName] and [keys].
///
/// Returns the number of objects that have been deleted. Isar web always
/// returns `keys.length`.
/// {@endtemplate}
@experimental
Future<int> deleteAllByIndex(String indexName, List<IndexKey> keys);
/// @nodoc
@protected
/// {@macro col_delete_all_by_index}
@experimental
int deleteAllByIndexSync(String indexName, List<IndexKey> keys);
/// {@template col_clear}
/// Remove all data in this collection and reset the auto increment value.
/// {@endtemplate}
Future<void> clear();
/// Remove all data in this collection and reset the auto increment value.
/// {@macro col_clear}
void clearSync();
/// Import a list of json objects.
/// {@template col_import_json_raw}
/// Import a list of json objects encoded as a byte array.
///
/// The json objects must have the same structure as the objects in this
/// collection. Otherwise an exception will be thrown.
/// {@endtemplate}
Future<void> importJsonRaw(Uint8List jsonBytes);
/// Import a list of json objects.
/// {@macro col_import_json_raw}
void importJsonRawSync(Uint8List jsonBytes);
/// {@template col_import_json}
/// Import a list of json objects.
///
/// The json objects must have the same structure as the objects in this
/// collection. Otherwise an exception will be thrown.
/// {@endtemplate}
Future<void> importJson(List<Map<String, dynamic>> json);
/// Import a list of json objects.
/// {@macro col_import_json}
void importJsonSync(List<Map<String, dynamic>> json);
/// Start building a query using the [QueryBuilder].
///
/// You can use where clauses to only return [distinct] results. If you want
/// to reverse the order, set [sort] to [Sort.desc].
QueryBuilder<OBJ, OBJ, QWhere> where({
bool distinct = false,
Sort sort = Sort.asc,
@@ -172,10 +253,10 @@ abstract class IsarCollection<OBJ> {
/// Start building a query using the [QueryBuilder].
///
/// Shortcut if you don't want to use indexes
/// Shortcut if you don't want to use where clauses.
QueryBuilder<OBJ, OBJ, QFilterCondition> filter() => where().filter();
/// Build a query dynamically. Can be used to build a custom query language.
/// Build a query dynamically for example to build a custom query language.
///
/// It is highly discouraged to use this method. Only in very special cases
/// should it be used. If you open an issue please always mention that you
@@ -196,28 +277,26 @@ abstract class IsarCollection<OBJ> {
String? property,
});
/// {@template col_count}
/// Returns the total number of objects in this collection.
///
/// For non-web apps, this method is extremely fast and independent of the
/// number of objects in the collection.
/// {@endtemplate}
Future<int> count();
/// Returns the total number of objects in this collection.
/// {@macro col_count}
int countSync();
/// {@template col_size}
/// Returns the size of the collection in bytes. Not supported on web.
///
/// For non-web apps, this method is extremely fast and independent of the
/// number of objects in the collection.
int countSync();
/// Returns the size of the collection in bytes. Not supported on web.
///
/// This method is extremely fast and independent of the number of objects in
/// the collection.
/// {@endtemplate}
Future<int> getSize({bool includeIndexes = false, bool includeLinks = false});
/// Returns the size of the collection in bytes. Not supported on web.
///
/// This method is extremely fast and independent of the number of objects in
/// the collection.
/// {@macro col_size}
int getSizeSync({bool includeIndexes = false, bool includeLinks = false});
/// Watch the collection for changes.
@@ -225,13 +304,14 @@ abstract class IsarCollection<OBJ> {
/// If [fireImmediately] is `true`, an event will be fired immediately.
Stream<void> watchLazy({bool fireImmediately = false});
/// Watch an object with [id] for changes.
/// Watch the object with [id] for changes. If a change occurs, the new object
/// will be returned in the stream.
///
/// Objects that don't exist (yet) can also be watched. If [fireImmediately]
/// is `true`, the object will be sent to the consumer immediately.
Stream<OBJ?> watchObject(Id id, {bool fireImmediately = false});
/// Watch an object with [id] for changes.
/// Watch the object with [id] for changes.
///
/// If [fireImmediately] is `true`, an event will be fired immediately.
Stream<void> watchObjectLazy(Id id, {bool fireImmediately = false});
+7 -6
View File
@@ -1,5 +1,5 @@
// coverage:ignore-file
// ignore_for_file: avoid_print, avoid_dynamic_calls
// ignore_for_file: avoid_print
part of isar;
@@ -243,11 +243,12 @@ abstract class _IsarConnect {
if (objects.isNotEmpty) {
dynamic object = objects.first;
for (var i = 0; i < keys.length; i++) {
final key = object is List ? int.parse(keys[i]) : keys[i];
if (i == keys.length - 1) {
object[key] = cEdit.value;
} else {
object = object[key];
if (i == keys.length - 1 && object is Map) {
object[keys[i]] = cEdit.value;
} else if (object is Map) {
object = object[keys[i]];
} else if (object is List) {
object = object[int.parse(keys[i])];
}
}
try {
+16 -17
View File
@@ -1,7 +1,7 @@
part of isar;
/// @nodoc
@protected
@sealed
abstract class IsarLinkBase<OBJ> {
/// Is the containing object managed by Isar?
bool get isAttached;
@@ -12,32 +12,33 @@ abstract class IsarLinkBase<OBJ> {
/// Has this link been loaded?
bool get isLoaded;
/// {@template link_load}
/// Loads the linked object(s) from the database
/// {@endtemplate}
Future<void> load();
/// Loads the linked object(s) from the database
/// {@macro link_load}
void loadSync();
/// {@template link_save}
/// Saves the linked object(s) to the database if there are changes.
///
/// Also puts new objects into the database that have id set to `null` or
/// `Isar.autoIncrement`.
/// {@endtemplate}
Future<void> save();
/// Saves the linked object(s) to the database if there are changes.
///
/// Also puts new objects into the database that have id set to `null` or
/// `Isar.autoIncrement`.
/// {@macro link_save}
void saveSync();
/// {@template link_reset}
/// Unlinks all linked object(s).
///
/// You can even call this method on links that have not been loaded yet.
/// {@endtemplate}
Future<void> reset();
/// Unlinks all linked object(s).
///
/// You can even call this method on links that have not been loaded yet.
/// {@macro link_reset}
void resetSync();
/// @nodoc
@@ -77,20 +78,19 @@ abstract class IsarLinks<OBJ> implements IsarLinkBase<OBJ>, Set<OBJ> {
@override
void loadSync({bool overrideChanges = true});
/// {@template links_update}
/// Creates and removes the specified links in the database.
///
/// This operation does not alter the state of the local copy of this link
/// and it can even be used without loading the link.
/// {@endtemplate}
Future<void> update({
Iterable<OBJ> link = const [],
Iterable<OBJ> unlink = const [],
bool reset = false,
});
/// Creates and removes the specified links in the database.
///
/// This operation does not alter the state of the local copy of this link
/// and it can even be used without loading the link.
/// {@macro links_update}
void updateSync({
Iterable<OBJ> link = const [],
Iterable<OBJ> unlink = const [],
@@ -100,15 +100,14 @@ abstract class IsarLinks<OBJ> implements IsarLinkBase<OBJ>, Set<OBJ> {
/// Starts a query for linked objects.
QueryBuilder<OBJ, OBJ, QAfterFilterCondition> filter();
/// {@template links_count}
/// Counts the linked objects in the database.
///
/// It does not take the local state into account and can even be used
/// without loading the link.
/// {@endtemplate}
Future<int> count() => filter().count();
/// Counts the linked objects in the database.
///
/// It does not take the local state into account and can even be used
/// without loading the link.
/// {@macro links_count}
int countSync() => filter().countSync();
}
+1
View File
@@ -2,6 +2,7 @@
part of isar;
/// @nodoc
@protected
abstract class IsarReader {
bool readBool(int offset);
+1
View File
@@ -2,6 +2,7 @@
part of isar;
/// @nodoc
@protected
abstract class IsarWriter {
void writeBool(int offset, bool? value);
@@ -1285,6 +1285,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char> name,
ffi.Pointer<ffi.Char> path,
ffi.Pointer<ffi.Char> schema_json,
int max_size_mib,
bool relaxed_durability,
int compact_min_file_size,
int compact_min_bytes,
@@ -1295,6 +1296,7 @@ class IsarCoreBindings {
name,
path,
schema_json,
max_size_mib,
relaxed_durability,
compact_min_file_size,
compact_min_bytes,
@@ -1309,6 +1311,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Int64,
ffi.Bool,
ffi.Uint32,
ffi.Uint32,
@@ -1319,6 +1322,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
int,
bool,
int,
int,
@@ -1329,6 +1333,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char> name,
ffi.Pointer<ffi.Char> path,
ffi.Pointer<ffi.Char> schema_json,
int max_size_mib,
bool relaxed_durability,
int compact_min_file_size,
int compact_min_bytes,
@@ -1340,6 +1345,7 @@ class IsarCoreBindings {
name,
path,
schema_json,
max_size_mib,
relaxed_durability,
compact_min_file_size,
compact_min_bytes,
@@ -1355,6 +1361,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Int64,
ffi.Bool,
ffi.Uint32,
ffi.Uint32,
@@ -1367,6 +1374,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>,
int,
bool,
int,
int,
+6 -2
View File
@@ -79,6 +79,7 @@ Future<Isar> openIsar({
required List<CollectionSchema<dynamic>> schemas,
String? directory,
required String name,
required int maxSizeMiB,
required bool relaxedDurability,
CompactCondition? compactOnLaunch,
}) async {
@@ -105,6 +106,7 @@ Future<Isar> openIsar({
namePtr,
dirPtr,
schemaStrPtr,
maxSizeMiB,
relaxedDurability,
compactMinFileSize ?? 0,
compactMinBytes ?? 0,
@@ -122,8 +124,9 @@ Future<Isar> openIsar({
Isar openIsarSync({
required List<CollectionSchema<dynamic>> schemas,
String? directory,
String name = 'isar',
bool relaxedDurability = true,
required String name,
required int maxSizeMiB,
required bool relaxedDurability,
CompactCondition? compactOnLaunch,
}) {
initializeCoreBinary();
@@ -146,6 +149,7 @@ Isar openIsarSync({
namePtr,
dirPtr,
schemaStrPtr,
maxSizeMiB,
relaxedDurability,
compactMinFileSize ?? 0,
compactMinBytes ?? 0,
+16 -28
View File
@@ -1,5 +1,3 @@
// ignore_for_file: prefer_initializing_formals
part of isar;
/// A where clause to traverse an Isar index.
@@ -20,20 +18,18 @@ class IdWhereClause extends WhereClause {
/// Where clause that matches all id values greater than the given [lower]
/// bound.
const IdWhereClause.greaterThan({
required Id lower,
required Id this.lower,
this.includeLower = true,
}) : lower = lower,
upper = null,
}) : upper = null,
includeUpper = true,
super._();
/// Where clause that matches all id values less than the given [upper]
/// bound.
const IdWhereClause.lessThan({
required Id upper,
required Id this.upper,
this.includeUpper = true,
}) : upper = upper,
lower = null,
}) : lower = null,
includeLower = true,
super._();
@@ -86,11 +82,10 @@ class IndexWhereClause extends WhereClause {
/// for equality.
const IndexWhereClause.greaterThan({
required this.indexName,
required IndexKey lower,
required IndexKey this.lower,
this.includeLower = true,
this.epsilon = Query.epsilon,
}) : lower = lower,
upper = null,
}) : upper = null,
includeUpper = true,
super._();
@@ -101,11 +96,10 @@ class IndexWhereClause extends WhereClause {
/// for equality.
const IndexWhereClause.lessThan({
required this.indexName,
required IndexKey upper,
required IndexKey this.upper,
this.includeUpper = true,
this.epsilon = Query.epsilon,
}) : upper = upper,
lower = null,
}) : lower = null,
includeLower = true,
super._();
@@ -127,14 +121,12 @@ class IndexWhereClause extends WhereClause {
/// are checked for equality.
const IndexWhereClause.between({
required this.indexName,
required IndexKey lower,
required IndexKey this.lower,
this.includeLower = true,
required IndexKey upper,
required IndexKey this.upper,
this.includeUpper = true,
this.epsilon = Query.epsilon,
}) : lower = lower,
upper = upper,
super._();
}) : super._();
/// The Isar name of the index to be used.
final String indexName;
@@ -244,12 +236,11 @@ class FilterCondition extends FilterOperation {
///
/// For lists, at least one of the values in the list has to match.
const FilterCondition.equalTo({
required String property,
required this.property,
required Object? value,
this.caseSensitive = true,
this.epsilon = Query.epsilon,
}) : type = FilterConditionType.equalTo,
property = property,
value1 = value,
include1 = true,
value2 = null,
@@ -578,20 +569,17 @@ class LinkFilter extends FilterOperation {
/// Create a filter condition based on a link.
const LinkFilter({
required this.linkName,
required FilterOperation filter,
}) : filter = filter,
lower = null,
required FilterOperation this.filter,
}) : lower = null,
upper = null,
super._();
/// Create a filter condition based on the number of linked objects.
const LinkFilter.length({
required this.linkName,
required int lower,
required int upper,
required int this.lower,
required int this.upper,
}) : filter = null,
lower = lower,
upper = upper,
assert(lower >= 0 && upper >= 0, 'Link length must be positive.'),
super._();
+19 -3
View File
@@ -47,6 +47,7 @@ impl IsarInstance {
name: &str,
dir: Option<&str>,
mut schema: Schema,
max_size_mib: usize,
relaxed_durability: bool,
compact_condition: Option<CompactCondition>,
) -> Result<Arc<Self>> {
@@ -65,6 +66,7 @@ impl IsarInstance {
dir,
instance_id,
schema,
max_size_mib,
relaxed_durability,
compact_condition,
)?;
@@ -107,6 +109,7 @@ impl IsarInstance {
dir: &str,
instance_id: u64,
mut schema: Schema,
max_size_mib: usize,
relaxed_durability: bool,
compact_condition: Option<CompactCondition>,
) -> Result<Self> {
@@ -115,8 +118,13 @@ impl IsarInstance {
Self::move_old_database(name, dir, &isar_file);
let db_count = schema.count_dbs() as u64 + 3;
let env = Env::create(&isar_file, db_count, relaxed_durability)
.map_err(|e| IsarError::EnvError { error: Box::new(e) })?;
let env = Env::create(
&isar_file,
db_count,
max_size_mib.max(1),
relaxed_durability,
)
.map_err(|e| IsarError::EnvError { error: Box::new(e) })?;
let txn = env.txn(true)?;
let mut manager = SchemaManager::create(instance_id, &txn)?;
@@ -154,7 +162,15 @@ impl IsarInstance {
if let Some(instance) = instance {
Ok(instance)
} else {
Self::open_internal(name, dir, instance_id, schema, relaxed_durability, None)
Self::open_internal(
name,
dir,
instance_id,
schema,
max_size_mib,
relaxed_durability,
None,
)
}
} else {
Ok(instance)
+13 -24
View File
@@ -11,10 +11,15 @@ pub struct Env {
unsafe impl Sync for Env {}
unsafe impl Send for Env {}
const MB: isize = 1 << 20;
const MIB: isize = 1 << 20;
impl Env {
pub fn create(path: &str, max_dbs: u64, relaxed_durability: bool) -> Result<Env> {
pub fn create(
path: &str,
max_dbs: u64,
max_size_mib: usize,
relaxed_durability: bool,
) -> Result<Env> {
let path = CString::new(path.as_bytes()).unwrap();
let mut env: *mut ffi::MDBX_env = ptr::null_mut();
unsafe {
@@ -31,15 +36,17 @@ impl Env {
flags |= ffi::MDBX_NOMETASYNC;
}
let max_size = (max_size_mib as isize).saturating_mul(MIB);
let mut err_code = 0;
for i in 0..9 {
mdbx_result(ffi::mdbx_env_set_geometry(
env,
MB,
MIB,
0,
(2000 - i * 200) * MB,
5 * MB,
20 * MB,
max_size - i * max_size / 10,
5 * MIB,
20 * MIB,
-1,
))?;
@@ -97,21 +104,3 @@ impl Drop for Env {
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_create() {
get_env();
}
pub fn get_env() -> Env {
let mut dir = std::env::temp_dir();
let r: u64 = rand::random();
dir.push(&r.to_string());
Env::create(dir.to_str().unwrap(), 50, false).unwrap()
}
}
+4 -1
View File
@@ -47,7 +47,10 @@ pub fn mdbx_result(err_code: c_int) -> Result<()> {
let err = CStr::from_ptr(err_raw);
Err(IsarError::MdbxError {
code: other,
message: err.to_str().unwrap().to_string(),
message: err
.to_str()
.unwrap_or("Cannot decode error message")
.to_string(),
})
},
}
+11 -2
View File
@@ -30,6 +30,7 @@ pub unsafe extern "C" fn isar_instance_create(
name: *const c_char,
path: *const c_char,
schema_json: *const c_char,
max_size_mib: i64,
relaxed_durability: bool,
compact_min_file_size: u32,
compact_min_bytes: u32,
@@ -51,8 +52,14 @@ pub unsafe extern "C" fn isar_instance_create(
})
};
let instance =
IsarInstance::open(name, path, schema, relaxed_durability, compact_condition)?;
let instance = IsarInstance::open(
name,
path,
schema,
max_size_mib as usize,
relaxed_durability,
compact_condition,
)?;
isar.write(Arc::into_raw(instance));
Ok(())
};
@@ -66,6 +73,7 @@ pub unsafe extern "C" fn isar_instance_create_async(
name: *const c_char,
path: *const c_char,
schema_json: *const c_char,
max_size_mib: i64,
relaxed_durability: bool,
compact_min_file_size: u32,
compact_min_bytes: u32,
@@ -86,6 +94,7 @@ pub unsafe extern "C" fn isar_instance_create_async(
name.0,
path.0,
schema_json.0,
max_size_mib,
relaxed_durability,
compact_min_file_size,
compact_min_bytes,
@@ -1,6 +1,6 @@
#Sat Aug 27 10:16:05 CEST 2022
#Sat Nov 12 16:30:49 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+5 -2
View File
@@ -5,12 +5,15 @@ Use the following commands to run the tests on a connected device:
### Unit tests
```
sh tool/setup_tests.sh
sh tool/prepare_tests.sh
sh tool/build.sh
dart test
```
### Integration tests
```
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart
sh tool/prepare_tests.sh
sh tool/build.sh
flutter test integration_test/integration_test.dart
```
+6 -5
View File
@@ -26,25 +26,23 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 31
compileSdkVersion 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.isar.isar_test"
minSdkVersion 16
targetSdkVersion 31
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
@@ -56,4 +54,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
@@ -0,0 +1,12 @@
package dev.isar.isar_test;
import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.integration_test.FlutterTestRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;
@RunWith(FlutterTestRunner.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class, true, false);
}
+2 -2
View File
@@ -2,7 +2,7 @@ buildscript {
ext.kotlin_version = '1.7.20'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
@@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
@@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
#Wed Nov 09 23:26:39 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
zipStoreBase=GRADLE_USER_HOME
@@ -1,4 +1,4 @@
// ignore_for_file: avoid_print
// ignore_for_file: avoid_print, depend_on_referenced_packages
import 'dart:async';
@@ -12,21 +12,7 @@ import 'all_tests.dart' as tests;
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final completer = Completer<List<String>>();
executeTests(completer);
testWidgets('Isar', (WidgetTester tester) async {
await tester.pumpWidget(Container());
final errors = await completer.future;
for (final err in errors) {
print(err);
}
expect(errors.isEmpty, true);
});
}
void executeTests(Completer<List<String>> completer) {
final completer = Completer<void>();
group('Integration test', () {
setUpAll(() async {
if (!kIsWeb) {
@@ -34,11 +20,15 @@ void executeTests(Completer<List<String>> completer) {
testTempPath = dir.path;
}
});
tearDownAll(() {
final result = testCount == 0 ? ['No tests were executed'] : testErrors;
completer.complete(result);
});
tearDownAll(completer.complete);
tests.main();
});
testWidgets('Isar', (WidgetTester tester) async {
await tester.pumpWidget(Container());
await completer.future;
expect(testCount > 0, true);
expect(testErrors, isEmpty);
});
}
-3
View File
@@ -1,3 +0,0 @@
import 'package:integration_test/integration_test_driver.dart';
void main() => integrationDriver();
+7
View File
@@ -3,3 +3,10 @@ export 'src/listener.dart';
export 'src/matchers.dart';
export 'src/sync_async_helper.dart';
export 'src/sync_future.dart';
export 'src/twitter/entities.dart';
export 'src/twitter/geo.dart';
export 'src/twitter/media.dart';
export 'src/twitter/tweet.dart';
export 'src/twitter/user.dart';
export 'src/twitter/util.dart';
+3 -1
View File
@@ -13,7 +13,7 @@ import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_api/src/backend/invoker.dart';
const bool kIsWeb = identical(0, 0.0);
const kIsWeb = identical(0, 0.0);
final testErrors = <String>[];
int testCount = 0;
@@ -111,6 +111,7 @@ Future<Isar> openTempIsar(
List<CollectionSchema<dynamic>> schemas, {
String? name,
String? directory,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
CompactCondition? compactOnLaunch,
bool closeAutomatically = true,
}) async {
@@ -124,6 +125,7 @@ Future<Isar> openTempIsar(
final isar = await tOpen(
schemas: schemas,
name: name ?? getRandomName(),
maxSizeMiB: maxSizeMiB,
directory: directory ?? testTempPath,
compactOnLaunch: compactOnLaunch,
);
+43 -13
View File
@@ -2,27 +2,57 @@ import 'dart:ffi';
import 'dart:io';
import 'package:isar/isar.dart';
import 'package:isar_test/src/rust_target.dart';
import 'package:path/path.dart' as path;
String getBinaryPath() {
final rootDir = path.dirname(path.dirname(Directory.current.path));
final target = getRustTarget();
final binaryName = Platform.isWindows
? 'isar.dll'
: Platform.isMacOS
? 'libisar.dylib'
: 'libisar.so';
return path.join(rootDir, 'target', target, 'debug', binaryName);
}
Future<void> init() async {
if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) {
final rootDir = path.dirname(path.dirname(Directory.current.path));
final binaryName = Platform.isWindows
? 'isar.dll'
: Platform.isMacOS
? 'libisar.dylib'
: 'libisar.so';
try {
await Isar.initializeIsarCore();
} catch (e) {
await Isar.initializeIsarCore(
libraries: {Abi.current(): getBinaryPath()},
libraries: {
Abi.macosArm64: path.join(
rootDir,
'target',
'aarch64-apple-darwin',
'release',
binaryName,
),
Abi.macosX64: path.join(
rootDir,
'target',
'x86_64-apple-darwin',
'release',
binaryName,
),
Abi.linuxArm64: path.join(
rootDir,
'target',
'aarch64-unknown-linux-gnu',
'release',
binaryName,
),
Abi.linuxX64: path.join(
rootDir,
'target',
'x86_64-unknown-linux-gnu',
'release',
binaryName,
),
Abi.windowsX64: path.join(
rootDir,
'target',
'x86_64-pc-windows-msvc',
'release',
binaryName,
),
},
);
}
}
+11 -1
View File
@@ -71,7 +71,17 @@ Matcher throwsIsarError([String? contains]) {
return throwsA(isIsarError(contains));
}
final Matcher throwsAssertionError = throwsA(isA<AssertionError>());
Matcher get throwsAssertionError {
var matcher = anything;
assert(
() {
matcher = throwsA(isA<AssertionError>());
return true;
}(),
'only in debug mode',
);
return matcher;
}
bool listEquals<T>(List<T>? a, List<T>? b) {
if (a == null) {
@@ -1,18 +0,0 @@
import 'dart:ffi';
String getRustTarget() {
switch (Abi.current()) {
case Abi.macosArm64:
return 'aarch64-apple-darwin';
case Abi.macosX64:
return 'x86_64-apple-darwin';
case Abi.linuxArm64:
return 'aarch64-unknown-linux-gnu';
case Abi.linuxX64:
return 'x86_64-unknown-linux-gnu';
case Abi.windowsX64:
return 'x86_64-pc-windows-gnu';
default:
throw UnsupportedError('Unsupported ABI: ${Abi.current()}');
}
}
@@ -10,6 +10,7 @@ Future<Isar> tOpen({
required List<CollectionSchema<dynamic>> schemas,
String? directory,
String name = Isar.defaultName,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
bool relaxedDurability = true,
CompactCondition? compactOnLaunch,
}) {
@@ -18,6 +19,7 @@ Future<Isar> tOpen({
schemas,
directory: directory,
name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch,
inspector: false,
@@ -28,6 +30,7 @@ Future<Isar> tOpen({
schemas,
directory: directory,
name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch,
inspector: false,
@@ -1,12 +1,11 @@
import 'package:isar/isar.dart';
import 'package:isar_test/src/twitter/entities.dart';
import 'package:isar_test/src/twitter/geo.dart';
import 'package:isar_test/src/twitter/media.dart';
import 'package:isar_test/src/twitter/user.dart';
import 'package:isar_test/src/twitter/util.dart';
import 'package:json_annotation/json_annotation.dart';
import 'entities.dart';
import 'geo.dart';
import 'media.dart';
import 'user.dart';
import 'util.dart';
part 'tweet.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake, createToJson: false)
@@ -1,9 +1,8 @@
import 'package:isar/isar.dart';
import 'package:isar_test/src/twitter/entities.dart';
import 'package:isar_test/src/twitter/util.dart';
import 'package:json_annotation/json_annotation.dart';
import 'entities.dart';
import 'util.dart';
part 'user.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake, createToJson: false)
+1 -1
View File
@@ -15,7 +15,7 @@ dependencies:
intl: 0.17.0
isar: any
isar_flutter_libs: any
json_annotation: ^4.2.0
json_annotation: ^4.7.0
meta: ^1.8.0
path: ^1.8.2
path_provider: ^2.0.10
@@ -54,10 +54,7 @@ void main() {
isarTestVm('.copyToFile() should keep the same content', () async {
final copiedDbFilename = getRandomName();
final copiedDbFile = File(
path.join(
isar.directory!,
'$copiedDbFilename.isar',
),
path.join(isar.directory!, '$copiedDbFilename.isar'),
);
await isar.copyToFile(copiedDbFile.path);
@@ -73,7 +70,6 @@ void main() {
copiedIsar.models.where(),
originalObjs,
);
await copiedDbFile.delete();
});
isarTestVm('.copyToFile() should compact copied file', () async {
@@ -1,11 +1,9 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'twitter/tweet.dart';
import 'package:test/test.dart';
void main() {
group('JSON', () {
@@ -0,0 +1,38 @@
import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
part 'max_size_test.g.dart';
@collection
class Model {
final Id id = Isar.autoIncrement;
final value = '123456789' * 1000;
}
void main() {
group('Max Size', () {
test('default', () async {
final isar = await openTempIsar([ModelSchema]);
await isar.writeTxn(() async {
await isar.models.putAll(List.filled(10000, Model()));
});
});
test('1MB', () async {
final isar = await openTempIsar([ModelSchema], maxSizeMiB: 1);
expect(
isar.writeTxn(() async {
await isar.models.putAll(List.filled(100, Model()));
}),
throwsIsarError('The database is full'),
);
await isar.writeTxn(() async {
await isar.models.putAll(List.filled(50, Model()));
});
});
});
}
@@ -1,8 +1,8 @@
import 'dart:math';
import 'package:flutter_test/flutter_test.dart';
import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
part 'long_string_test.g.dart';
@@ -37,49 +37,51 @@ String _randomStr(int length) {
}
void main() {
late Isar isar;
group('Long String', () {
late Isar isar;
setUp(() async {
isar = await openTempIsar([StringModelSchema]);
});
isarTest('Long String', () async {
final models = <StringModel>[
for (var i = 0; i < 100; i++)
StringModel(
string: '${_randomStr(50000)}test$i${_randomStr(50000)}',
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
setUp(() async {
isar = await openTempIsar([StringModelSchema]);
});
await qEqual(isar.stringModels.where(), models);
isarTest('Single', () async {
final models = <StringModel>[
for (var i = 0; i < 100; i++)
StringModel(
string: '${_randomStr(50000)}test$i${_randomStr(50000)}',
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
});
await qEqual(
isar.stringModels.filter().stringContains('test75'),
[models[75]],
);
await qEqual(
isar.stringModels.filter().stringMatches('*test66*'),
[models[66]],
);
});
await qEqual(isar.stringModels.where(), models);
isarTest('Long String List', () async {
final models = <StringModel>[
for (var i = 0; i < 10; i++)
StringModel(
stringList: [
for (var j = 0; j < 100; j++)
'${_randomStr(10000)}test${i}_$j${_randomStr(10000)}',
],
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
await qEqual(
isar.stringModels.filter().stringContains('test75'),
[models[75]],
);
await qEqual(
isar.stringModels.filter().stringMatches('*test66*'),
[models[66]],
);
});
await qEqual(isar.stringModels.where(), models);
isarTest('List', () async {
final models = <StringModel>[
for (var i = 0; i < 10; i++)
StringModel(
stringList: [
for (var j = 0; j < 100; j++)
'${_randomStr(10000)}test${i}_$j${_randomStr(10000)}',
],
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
});
await qEqual(isar.stringModels.where(), models);
});
});
}
@@ -5,9 +5,6 @@ import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
import 'twitter/entities.dart';
import 'twitter/tweet.dart';
Future<List<Tweet>> downloadTweets(String dir, int index) async {
final i = index < 10 ? '0$index' : '$index';
final file = File('$dir/tweets$i.json');
@@ -9,12 +9,17 @@ void main() {
.toList();
final imports = files.map((String e) {
return "import '$e' as ${e.split('.')[0].replaceAll(p.separator, '_')};";
final dartPath = e.replaceAll(p.separator, '/');
final name = e.split('.')[0].replaceAll(p.separator, '_');
return "import '../$dartPath' as $name;";
}).join('\n');
final calls = files.map((String e) {
final content = File(e).readAsStringSync();
final call = "${e.split('.')[0].replaceAll(p.separator, '_')}.main();";
var call = "${e.split('.')[0].replaceAll(p.separator, '_')}.main();";
if (e.contains('stress')) {
call = 'if (stress) $call';
}
if (content.startsWith("@TestOn('vm')")) {
return 'if (!kIsWeb) $call';
} else {
@@ -29,9 +34,11 @@ void main() {
$imports
void main() {
const stress = bool.fromEnvironment('STRESS');
$calls
}
""";
File('all_tests.dart').writeAsStringSync(code);
Directory('integration_test').createSync();
File('integration_test${p.separator}all_tests.dart').writeAsStringSync(code);
}
@@ -1,53 +0,0 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:isar_test/src/rust_target.dart';
import 'package:path/path.dart' as path;
void main() {
print('Building Isar Core');
final rootDir = path.dirname(path.dirname(Directory.current.path));
final rustResult = Process.runSync(
'cargo',
['build', '--target', getRustTarget()],
runInShell: true,
workingDirectory: rootDir,
);
if (rustResult.exitCode != 0) {
throw Exception('Cargo build failed: ${rustResult.stderr}');
}
/*print('Building Isar Web');
final webDir = path.join(rootDir, 'packages', 'isar_web');
final npmResult = Process.runSync(
'npm',
['run', 'build'],
runInShell: true,
workingDirectory: webDir,
);
print(webDir);
if (npmResult.exitCode != 0) {
throw Exception('Npm build failed: ${npmResult.stderr}');
}
print('Packing Isar Web');
final webpackResult = Process.runSync(
'npx',
['webpack', '--mode', 'development'],
runInShell: true,
workingDirectory: webDir,
);
if (webpackResult.exitCode != 0) {
throw Exception('Webpack build failed: ${npmResult.stderr}');
}
print('Writing Isar Web in the test dir');
final isarWebSrc =
File(path.join(webDir, 'dist', 'index.js')).readAsStringSync();
final escaped = isarWebSrc.replaceAll(r'$', r'\$');
File('lib/src/isar_web_src.dart').writeAsStringSync(
'// ignore_for_file: unnecessary_string_escapes\n'
"const isarWebSrc = '''\n$escaped''';",
);*/
}
+11 -4
View File
@@ -50,7 +50,7 @@ impl ParseCallbacks for Callbacks {
}
const LIBMDBX_REPO: &str = "https://github.com/isar/libmdbx.git";
const LIBMDBX_TAG: &str = "v0.12.2";
const LIBMDBX_TAG: &str = "v0.11.2";
fn main() {
println!("cargo:rerun-if-changed=build.rs");
@@ -61,13 +61,19 @@ fn main() {
Command::new("git")
.arg("clone")
.arg(LIBMDBX_REPO)
.arg("--depth")
.arg("1")
.arg("--branch")
.arg(LIBMDBX_TAG)
.output()
.unwrap();
/*Command::new("git")
.current_dir("libmdbx")
.arg("revert")
.arg("-n")
.arg("fe20de")
.output()
.unwrap();*/
Command::new("make")
.arg("release-assets")
.current_dir("libmdbx")
@@ -136,7 +142,8 @@ fn main() {
.define("MDBX_LOCK_SUFFIX", "\".lock\"")
.define("MDBX_TXN_CHECKOWNER", "0")
.define("MDBX_OSX_SPEED_INSTEADOF_DURABILITY", "1")
.define("NDEBUG", "1")
.define("MDBX_HAVE_BUILTIN_CPU_SUPPORTS", "0")
//.define("MDX_DEBUG", "7")
.file(mdbx.join("mdbx.c"))
.compile("libmdbx.a");
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/shuname
arch=$(uname -m)
if [ `uname` = "Linux" ] ;
then
if [ $arch = "x86_64" ] ;
then
cargo build --target x86_64-unknown-linux-gnu --release
else
cargo build --target aarch64-unknown-linux-gnu --release
fi
elif [ `uname` = "Darwin" ] ;
then
if [[ $arch == x86_64* ]]; then
cargo build --target x86_64-apple-darwin --release
else
cargo build --target aarch64-apple-darwin --release
fi
else
cargo build --target x86_64-pc-windows-msvc --release
fi
+2 -2
View File
@@ -45,8 +45,8 @@ elif [ "$1" = "x64" ]; then
mv "target/x86_64-linux-android/release/libisar.so" "libisar_android_x64.so"
elif [ "$1" = "armv7" ]; then
rustup target add armv7-linux-androideabi
cargo build --target armv7-linux-androideabi --release
mv "target/armv7-linux-androideabi/release/libisar.so" "libisar_android_armv7.so"
cargo build --target armv7-linux-androideabi
mv "target/armv7-linux-androideabi/debug/libisar.so" "libisar_android_armv7.so"
else
rustup target add aarch64-linux-android
cargo build --target aarch64-linux-android --release
+6
View File
@@ -0,0 +1,6 @@
cd packages/isar_test
flutter pub get
dart tool/generate_long_double_test.dart
dart tool/generate_all_tests.dart
flutter pub run build_runner build