Compare commits
14 Commits
e24ef0c3d5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 83be3c8826 | |||
| 48ff0784f4 | |||
| 9d9e6ddc1e | |||
| 5e867e30ee | |||
| 3d3b047097 | |||
| 7e55015948 | |||
| 2a469e10e8 | |||
| 9bdff5ae01 | |||
| c1087fe12e | |||
| 96edecdf2a | |||
| 086e41f2bb | |||
| cad98aab54 | |||
| db88155e95 | |||
| b07e526b6a |
24
.woodpecker.yml
Normal file
24
.woodpecker.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
pipeline:
|
||||
lint:
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
# Proxy requests to pub.dev using pubcached
|
||||
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||
- dart analyze --fatal-infos --fatal-warnings
|
||||
test:
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
# Proxy requests to pub.dev using pubcached
|
||||
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||
- dart test
|
||||
notify:
|
||||
image: git.polynom.me/papatutuwawa/woodpecker-xmpp
|
||||
settings:
|
||||
xmpp_tls: 1
|
||||
xmpp_is_muc: 1
|
||||
xmpp_recipient: moxxy-build@muc.moxxy.org
|
||||
xmpp_alias: 2Bot
|
||||
secrets: [ xmpp_jid, xmpp_password, xmpp_server ]
|
||||
when:
|
||||
status:
|
||||
- failure
|
||||
@@ -1,4 +1,8 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
include: package:very_good_analysis/analysis_options.yaml
|
||||
linter:
|
||||
rules:
|
||||
public_member_api_docs: false
|
||||
lines_longer_than_80_chars: false
|
||||
use_setters_to_change_properties: false
|
||||
avoid_positional_boolean_parameters: false
|
||||
avoid_bool_literals_in_conditional_expressions: false
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1649676176,
|
||||
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||
"lastModified": 1656065134,
|
||||
"narHash": "sha256-oc6E6ByIw3oJaIyc67maaFcnjYOz1mMcOtHxbEf9NwQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
|
||||
"rev": "bee6a7250dd1b01844a2de7e02e4df7d8a0a206c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,16 +17,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1650034868,
|
||||
"narHash": "sha256-OAaf5BdWKGXTXvYnbvJuoQjSWnVKgt1cIOChF0MFt2o=",
|
||||
"owner": "PapaTutuWawa",
|
||||
"lastModified": 1692311226,
|
||||
"narHash": "sha256-mRzNup0PIUD6YxbrYvjzL7f+1oaOGy9nmGCV3AZkQus=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "13a5646d450052b88067cab37b198f8a2737e431",
|
||||
"rev": "ef8288935ba859fc3b30632fa6e04705f81b9c2a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "PapaTutuWawa",
|
||||
"ref": "nixos-unstable-flutter-2.13.0-0.1.pre",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
26
flake.nix
26
flake.nix
@@ -1,43 +1,21 @@
|
||||
{
|
||||
description = "moxlib";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:PapaTutuWawa/nixpkgs/nixos-unstable-flutter-2.13.0-0.1.pre";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.android_sdk.accept_license = true;
|
||||
};
|
||||
android = pkgs.androidenv.composeAndroidPackages {
|
||||
# TODO: Find a way to pin these
|
||||
#toolsVersion = "26.1.1";
|
||||
#platformToolsVersion = "31.0.3";
|
||||
#buildToolsVersions = [ "31.0.0" ];
|
||||
#includeEmulator = true;
|
||||
#emulatorVersion = "30.6.3";
|
||||
platformVersions = [ "28" ];
|
||||
includeSources = false;
|
||||
includeSystemImages = true;
|
||||
systemImageTypes = [ "default" ];
|
||||
abiVersions = [ "x86_64" ];
|
||||
includeNDK = false;
|
||||
useGoogleAPIs = false;
|
||||
useGoogleTVAddOns = false;
|
||||
};
|
||||
pinnedJDK = pkgs.jdk11;
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
flutterPackages.beta pinnedJDK android.platform-tools flutterPackages.dart-beta # Flutter
|
||||
dart # Dart
|
||||
gitlint jq # Code hygiene
|
||||
ripgrep # General utilities
|
||||
];
|
||||
|
||||
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||
JAVA_HOME = pinnedJDK;
|
||||
ANDROID_AVD_HOME = (toString ./.) + "/.android/avd";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
class NoTransitionPossibleException implements Exception {
|
||||
@override
|
||||
String errMsg() => "The transition graph allows no transition";
|
||||
}
|
||||
|
||||
/// A deterministic finite automaton. [T] is the state type while
|
||||
/// [I] is the input type.
|
||||
/// Edges of the node must be added with [addTransition]. If a trap state
|
||||
/// is required, it can be set in the constructor.
|
||||
class DeterministicFiniteAutomaton<T, I> {
|
||||
/// The current state of the DFA
|
||||
T _state;
|
||||
/// The edges of the DFA: State x Input -> State
|
||||
Map<T, Map<I, T>> _transitions;
|
||||
/// Trap state
|
||||
T? trapState;
|
||||
|
||||
/// The argument is the initial state
|
||||
DeterministicFiniteAutomaton(this._state, { this.trapState }) : _transitions = {};
|
||||
|
||||
T get state => _state;
|
||||
|
||||
void addTransition(T oldState, I input, T newState) {
|
||||
assert(oldState != trapState);
|
||||
// These are handled implicitly if no transition has been found
|
||||
assert(newState != trapState);
|
||||
|
||||
if (!_transitions.containsKey(oldState)) {
|
||||
_transitions[oldState] = {};
|
||||
}
|
||||
|
||||
_transitions[oldState]![input] = newState;
|
||||
}
|
||||
|
||||
/// Transition the DFA based on its current state and the input [input].
|
||||
void onInput(I input) {
|
||||
final newState = _transitions[_state]?[input];
|
||||
if (newState == null) {
|
||||
// Go to the trap state if we can
|
||||
if (trapState != null) {
|
||||
_state = trapState!;
|
||||
return;
|
||||
} else {
|
||||
throw NoTransitionPossibleException();
|
||||
}
|
||||
}
|
||||
|
||||
_state = newState;
|
||||
}
|
||||
|
||||
/// Returns where [input] would take the automaton to. Returns null if no transition
|
||||
/// is possible, ignoring trap transitions.
|
||||
T? peekTransition(I input) {
|
||||
if (!_transitions.containsKey(_state) || !_transitions[_state]!.containsKey(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _transitions[_state]![input]!;
|
||||
}
|
||||
}
|
||||
|
||||
typedef MealyAutomatonCallback<T, I> = void Function(T oldState, I input);
|
||||
class MealyAutomaton<T, I> {
|
||||
/// The base automaton
|
||||
final DeterministicFiniteAutomaton<T, I> _automaton;
|
||||
/// Mapping of State x Input -> Output callback
|
||||
Map<T, Map<I, MealyAutomatonCallback<T, I>>> _outputs;
|
||||
/// Trap state
|
||||
MealyAutomatonCallback<T, I>? trapCallback;
|
||||
|
||||
// TODO: Assert that trapState != null implies trapCallback != null.
|
||||
MealyAutomaton(T initialState, { T? trapState, this.trapCallback })
|
||||
: _outputs = {},
|
||||
_automaton = DeterministicFiniteAutomaton(initialState, trapState: trapState);
|
||||
|
||||
T get state => _automaton.state;
|
||||
|
||||
void addTransition(T oldState, I input, T newState, MealyAutomatonCallback<T, I> callback) {
|
||||
_automaton.addTransition(oldState, input, newState);
|
||||
|
||||
if (!_outputs.containsKey(oldState)) {
|
||||
_outputs[oldState] = {};
|
||||
}
|
||||
|
||||
_outputs[oldState]![input] = callback;
|
||||
}
|
||||
|
||||
void onInput(I input) {
|
||||
final _state = _automaton.state;
|
||||
if (_automaton.peekTransition(input) == null && trapCallback == null) {
|
||||
throw new NoTransitionPossibleException();
|
||||
}
|
||||
|
||||
final callback = _outputs[_state]?[input] ?? trapCallback!;
|
||||
|
||||
_automaton.onInput(input);
|
||||
callback(_state, input);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:synchronized/synchronized.dart";
|
||||
import "package:uuid/uuid.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:meta/meta.dart";
|
||||
|
||||
/// Interface to allow arbitrary data to be sent as long as it can be
|
||||
/// JSON serialized/deserialized.
|
||||
class JsonImplementation {
|
||||
JsonImplementation();
|
||||
|
||||
Map<String, dynamic> toJson() => {};
|
||||
factory JsonImplementation.fromJson(Map<String, dynamic> json) {
|
||||
return JsonImplementation();
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper class that adds an ID to the data packet to be sent.
|
||||
class DataWrapper<T extends JsonImplementation> {
|
||||
final String id;
|
||||
final T data;
|
||||
|
||||
const DataWrapper(
|
||||
this.id,
|
||||
this.data
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"data": data.toJson()
|
||||
};
|
||||
|
||||
static DataWrapper fromJson<T extends JsonImplementation>(Map<String, dynamic> json) => DataWrapper<T>(
|
||||
json["id"]! as String,
|
||||
json["data"]! as T
|
||||
);
|
||||
|
||||
DataWrapper reply(T newData) => DataWrapper(id, newData);
|
||||
}
|
||||
|
||||
/// This class is useful in contexts where data is sent between two parties, e.g. the
|
||||
/// UI and the background service and a correlation between requests and responses is
|
||||
/// to be enabled.
|
||||
///
|
||||
/// awaiting [sendData] will return a [Future] that will resolve to the reresponse when
|
||||
/// received via [onData].
|
||||
abstract class AwaitableDataSender<
|
||||
S extends JsonImplementation,
|
||||
R extends JsonImplementation
|
||||
> {
|
||||
final Lock _lock;
|
||||
final Map<String, Completer<R>> _awaitables;
|
||||
final Uuid _uuid;
|
||||
final Logger _log;
|
||||
|
||||
@mustCallSuper
|
||||
AwaitableDataSender() : _awaitables = {}, _uuid = const Uuid(), _lock = Lock(), _log = Logger("AwaitableDataSender");
|
||||
|
||||
@visibleForTesting
|
||||
Map<String, Completer> getAwaitables() => _awaitables;
|
||||
|
||||
/// Called after an awaitable has been added.
|
||||
@visibleForTesting
|
||||
void onAdd() {}
|
||||
|
||||
/// NOTE: Must be overwritten by the actual implementation
|
||||
@visibleForOverriding
|
||||
Future<void> sendDataImpl(DataWrapper data);
|
||||
|
||||
/// Sends [data] using [sendDataImpl]. If [awaitable] is true, then a
|
||||
/// Future will be returned that can be used to await a response. If it
|
||||
/// is false, then null will be imediately resolved.
|
||||
Future<R?> sendData(S data, { bool awaitable = true, @visibleForTesting String? id }) async {
|
||||
final _id = id ?? _uuid.v4();
|
||||
Future<R?> future = Future.value(null);
|
||||
_log.fine("sendData: Waiting to acquire lock...");
|
||||
await _lock.synchronized(() async {
|
||||
_log.fine("sendData: Done");
|
||||
if (awaitable) {
|
||||
_awaitables[_id] = Completer();
|
||||
onAdd();
|
||||
}
|
||||
|
||||
await sendDataImpl(
|
||||
DataWrapper<S>(
|
||||
_id,
|
||||
data
|
||||
)
|
||||
);
|
||||
|
||||
if (awaitable) {
|
||||
future = _awaitables[_id]!.future;
|
||||
}
|
||||
|
||||
_log.fine("sendData: Releasing lock...");
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/// Should be called when a [DataWrapper] has been received. Will resolve
|
||||
/// the promise received from [sendData].
|
||||
Future<bool> onData(DataWrapper<R> data) async {
|
||||
bool found = false;
|
||||
Completer? completer;
|
||||
_log.fine("onData: Waiting to acquire lock...");
|
||||
await _lock.synchronized(() async {
|
||||
_log.fine("onData: Done");
|
||||
completer = _awaitables[data.id];
|
||||
if (completer != null) {
|
||||
_awaitables.remove(data.id);
|
||||
found = true;
|
||||
}
|
||||
|
||||
_log.fine("onData: Releasing lock");
|
||||
});
|
||||
|
||||
if (found) {
|
||||
completer!.complete(data.data);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
library moxlib;
|
||||
|
||||
export "awaitabledatasender.dart";
|
||||
export "automaton.dart";
|
||||
export "math.dart";
|
||||
export 'src/awaitabledatasender.dart';
|
||||
export 'src/math.dart';
|
||||
export 'src/result.dart';
|
||||
|
||||
136
lib/src/awaitabledatasender.dart
Normal file
136
lib/src/awaitabledatasender.dart
Normal file
@@ -0,0 +1,136 @@
|
||||
import 'dart:async';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Interface to allow arbitrary data to be sent as long as it can be
|
||||
/// JSON serialized/deserialized.
|
||||
class JsonImplementation {
|
||||
JsonImplementation();
|
||||
|
||||
// ignore: avoid_unused_constructor_parameters
|
||||
factory JsonImplementation.fromJson(Map<String, dynamic> json) {
|
||||
return JsonImplementation();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {};
|
||||
}
|
||||
|
||||
/// Wrapper class that adds an ID to the data packet to be sent.
|
||||
class DataWrapper<T extends JsonImplementation> {
|
||||
const DataWrapper(
|
||||
this.id,
|
||||
this.data,
|
||||
);
|
||||
|
||||
/// The id of the data packet.
|
||||
final String id;
|
||||
|
||||
/// The actual data.
|
||||
final T data;
|
||||
|
||||
Map<String, dynamic> toJson() => {'id': id, 'data': data.toJson()};
|
||||
|
||||
static DataWrapper fromJson<T extends JsonImplementation>(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
DataWrapper<T>(
|
||||
json['id']! as String,
|
||||
json['data']! as T,
|
||||
);
|
||||
|
||||
DataWrapper reply(T newData) => DataWrapper(id, newData);
|
||||
}
|
||||
|
||||
/// This class is useful in contexts where data is sent between two parties, e.g. the
|
||||
/// UI and the background service and a correlation between requests and responses is
|
||||
/// to be enabled.
|
||||
///
|
||||
/// awaiting [sendData] will return a [Future] that will resolve to the reresponse when
|
||||
/// received via [onData].
|
||||
abstract class AwaitableDataSender<S extends JsonImplementation,
|
||||
R extends JsonImplementation> {
|
||||
@mustCallSuper
|
||||
AwaitableDataSender();
|
||||
|
||||
/// A mapping of ID to completer for pending requests.
|
||||
final Map<String, Completer<R>> _awaitables = {};
|
||||
|
||||
/// Critical section for accessing [AwaitableDataSender._awaitables].
|
||||
final Lock _lock = Lock();
|
||||
|
||||
/// A UUID object for generating UUIDs.
|
||||
final Uuid _uuid = const Uuid();
|
||||
|
||||
/// A logger.
|
||||
final Logger _log = Logger('AwaitableDataSender');
|
||||
|
||||
@visibleForTesting
|
||||
Map<String, Completer<R>> getAwaitables() => _awaitables;
|
||||
|
||||
/// Called after an awaitable has been added.
|
||||
@visibleForTesting
|
||||
void onAdd() {}
|
||||
|
||||
/// NOTE: Must be overwritten by the actual implementation
|
||||
@visibleForOverriding
|
||||
Future<void> sendDataImpl(DataWrapper data);
|
||||
|
||||
/// Sends [data] using [sendDataImpl]. If [awaitable] is true, then a
|
||||
/// Future will be returned that can be used to await a response. If it
|
||||
/// is false, then null will be imediately resolved.
|
||||
Future<R?> sendData(
|
||||
S data, {
|
||||
bool awaitable = true,
|
||||
@visibleForTesting String? id,
|
||||
}) async {
|
||||
// ignore: no_leading_underscores_for_local_identifiers
|
||||
final _id = id ?? _uuid.v4();
|
||||
var future = Future<R?>.value();
|
||||
_log.fine('sendData: Waiting to acquire lock...');
|
||||
await _lock.synchronized(() async {
|
||||
_log.fine('sendData: Done');
|
||||
if (awaitable) {
|
||||
_awaitables[_id] = Completer();
|
||||
onAdd();
|
||||
}
|
||||
|
||||
await sendDataImpl(
|
||||
DataWrapper<S>(
|
||||
_id,
|
||||
data,
|
||||
),
|
||||
);
|
||||
|
||||
if (awaitable) {
|
||||
future = _awaitables[_id]!.future;
|
||||
}
|
||||
|
||||
_log.fine('sendData: Releasing lock...');
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/// Should be called when a [DataWrapper] has been received. Will resolve
|
||||
/// the promise received from [sendData].
|
||||
Future<bool> onData(DataWrapper<R> data) async {
|
||||
_log.fine('onData: Waiting to acquire lock...');
|
||||
final completer = await _lock.synchronized(() async {
|
||||
_log.fine('onData: Done');
|
||||
final c = _awaitables[data.id];
|
||||
if (c != null) {
|
||||
_awaitables.remove(data.id);
|
||||
return c;
|
||||
}
|
||||
|
||||
_log.fine('onData: Releasing lock');
|
||||
return null;
|
||||
});
|
||||
|
||||
completer?.complete(data.data);
|
||||
|
||||
return completer != null;
|
||||
}
|
||||
}
|
||||
24
lib/src/result.dart
Normal file
24
lib/src/result.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
/// Holds a value of either [T] or [V].
|
||||
class Result<T, V> {
|
||||
/// Constructs a result. [_data] must be either of type [T] or [V].
|
||||
const Result(this._data)
|
||||
: assert(
|
||||
_data is T || _data is V,
|
||||
'Invalid data type $_data: Must be either $T or $V',
|
||||
);
|
||||
final dynamic _data;
|
||||
|
||||
/// Returns true if the data contained within is of type [S]. If not, returns false.
|
||||
bool isType<S>() => _data is S;
|
||||
|
||||
/// Returns the data contained within cast to [S]. Before doing this call, it's recommended
|
||||
/// to check isType<S>() first.
|
||||
S get<S>() {
|
||||
assert(_data is S, 'Data is not $S');
|
||||
|
||||
return _data as S;
|
||||
}
|
||||
|
||||
/// Returns the runtime type of the data.
|
||||
Object get dataRuntimeType => _data.runtimeType;
|
||||
}
|
||||
22
pubspec.yaml
22
pubspec.yaml
@@ -1,21 +1,19 @@
|
||||
name: moxlib
|
||||
description: A collection of code for sharing between various moxxy libraries. Not inteded for outside use.
|
||||
version: 0.1.3
|
||||
description: A collection of code for sharing between various moxxy libraries. Not intended for outside use.
|
||||
version: 0.2.0
|
||||
homepage: https://codeberg.org/moxxy/moxlib
|
||||
publish_to: https://pub.polynom.me
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0-266.1.beta <3.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
logging: 1.0.2
|
||||
synchronized: 3.0.0
|
||||
uuid: 3.0.5
|
||||
meta: 1.7.0
|
||||
logging: ^1.0.2
|
||||
meta: ^1.7.0
|
||||
synchronized: ^3.0.0
|
||||
uuid: ^3.0.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.0
|
||||
test: 1.20.1
|
||||
test: ^1.20.1
|
||||
very_good_analysis: ^5.0.0
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import "package:moxlib/automaton.dart";
|
||||
|
||||
import "package:test/test.dart";
|
||||
|
||||
enum States {
|
||||
a, b, c, trap
|
||||
}
|
||||
|
||||
void main() {
|
||||
test("Test a simple DFA", () {
|
||||
final automaton = DeterministicFiniteAutomaton<States, int>(States.a);
|
||||
automaton.addTransition(States.a, 1, States.b);
|
||||
automaton.addTransition(States.b, 2, States.c);
|
||||
automaton.addTransition(States.c, 3, States.a);
|
||||
|
||||
expect(automaton.state, States.a);
|
||||
automaton.onInput(1);
|
||||
expect(automaton.state, States.b);
|
||||
automaton.onInput(2);
|
||||
expect(automaton.state, States.c);
|
||||
automaton.onInput(3);
|
||||
expect(automaton.state, States.a);
|
||||
});
|
||||
|
||||
test("Test a simple DFA with a trap state", () {
|
||||
final automaton = DeterministicFiniteAutomaton<States, int>(States.a, trapState: States.trap);
|
||||
automaton.addTransition(States.a, 1, States.b);
|
||||
automaton.addTransition(States.b, 2, States.c);
|
||||
automaton.addTransition(States.c, 3, States.a);
|
||||
|
||||
expect(automaton.state, States.a);
|
||||
automaton.onInput(1);
|
||||
expect(automaton.state, States.b);
|
||||
automaton.onInput(2);
|
||||
expect(automaton.state, States.c);
|
||||
automaton.onInput(4);
|
||||
expect(automaton.state, States.trap);
|
||||
|
||||
// Transitioning away from the trap state should not be possible
|
||||
automaton.onInput(5);
|
||||
expect(automaton.state, States.trap);
|
||||
});
|
||||
|
||||
test("Test a simple Mealy Automaton", () {
|
||||
bool called = false;
|
||||
final callback = (state, input) {
|
||||
called = true;
|
||||
};
|
||||
final automaton = MealyAutomaton<States, int>(States.a);
|
||||
|
||||
automaton.addTransition(States.a, 1, States.b, callback);
|
||||
|
||||
automaton.onInput(1);
|
||||
|
||||
expect(automaton.state, States.b);
|
||||
expect(called, true);
|
||||
});
|
||||
|
||||
test("Test a simple Mealy Automaton with a trap state", () {
|
||||
bool called = false;
|
||||
bool trapCalled = false;
|
||||
final callback = (state, input) {
|
||||
called = true;
|
||||
};
|
||||
final trapCallback = (state, input) {
|
||||
trapCalled = true;
|
||||
};
|
||||
final automaton = MealyAutomaton<States, int>(States.a, trapState: States.trap, trapCallback: trapCallback);
|
||||
|
||||
automaton.addTransition(States.a, 1, States.b, callback);
|
||||
|
||||
automaton.onInput(1);
|
||||
expect(called, true);
|
||||
|
||||
automaton.onInput(1);
|
||||
expect(automaton.state, States.trap);
|
||||
expect(trapCalled, true);
|
||||
});
|
||||
}
|
||||
@@ -1,29 +1,23 @@
|
||||
import "package:moxlib/awaitabledatasender.dart";
|
||||
|
||||
import "package:test/test.dart";
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestDataType implements JsonImplementation {
|
||||
final String data;
|
||||
|
||||
TestDataType(this.data);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
"data": data
|
||||
};
|
||||
factory TestDataType.fromJson(Map<String, dynamic> json) =>
|
||||
TestDataType(json['data']! as String);
|
||||
|
||||
factory TestDataType.fromJson(Map<String, dynamic> json) => TestDataType(
|
||||
json["data"]!
|
||||
);
|
||||
final String data;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'data': data};
|
||||
}
|
||||
|
||||
class FakeAwaitableDataSender<
|
||||
S extends JsonImplementation,
|
||||
R extends JsonImplementation
|
||||
> extends AwaitableDataSender<S, R> {
|
||||
final void Function()? onAddFunc;
|
||||
class FakeAwaitableDataSender<S extends JsonImplementation,
|
||||
R extends JsonImplementation> extends AwaitableDataSender<S, R> {
|
||||
FakeAwaitableDataSender({this.onAddFunc}) : super();
|
||||
|
||||
FakeAwaitableDataSender({ this.onAddFunc }) : super();
|
||||
final void Function()? onAddFunc;
|
||||
|
||||
@override
|
||||
Future<void> sendDataImpl(DataWrapper data) async {}
|
||||
@@ -35,39 +29,41 @@ class FakeAwaitableDataSender<
|
||||
}
|
||||
|
||||
void main() {
|
||||
test("Sending an event without awaiting it", () async {
|
||||
final handler = FakeAwaitableDataSender<TestDataType, TestDataType>();
|
||||
final result = await handler.sendData(TestDataType("hallo"), awaitable: false);
|
||||
test('Sending an event without awaiting it', () async {
|
||||
final handler = FakeAwaitableDataSender<TestDataType, TestDataType>();
|
||||
final result =
|
||||
await handler.sendData(TestDataType('hallo'), awaitable: false);
|
||||
|
||||
expect(result, null);
|
||||
expect(handler.getAwaitables().length, 0);
|
||||
expect(result, null);
|
||||
expect(handler.getAwaitables().length, 0);
|
||||
});
|
||||
|
||||
test("Sending an event without awaiting it", () async {
|
||||
final handler = FakeAwaitableDataSender<TestDataType, TestDataType>();
|
||||
const id = "abc123";
|
||||
final result = handler.sendData(TestDataType("hallo"), awaitable: true, id: id);
|
||||
await handler.onData(DataWrapper(id, TestDataType("welt")));
|
||||
test('Sending an event without awaiting it', () async {
|
||||
final handler = FakeAwaitableDataSender<TestDataType, TestDataType>();
|
||||
const id = 'abc123';
|
||||
final result =
|
||||
handler.sendData(TestDataType('hallo'), awaitable: true, id: id);
|
||||
await handler.onData(DataWrapper(id, TestDataType('welt')));
|
||||
|
||||
expect((await result)!.data, "welt");
|
||||
expect(handler.getAwaitables().length, 0);
|
||||
expect((await result)!.data, 'welt');
|
||||
expect(handler.getAwaitables().length, 0);
|
||||
});
|
||||
|
||||
test("Queue multiple data packets and resolve in reverse order", () async {
|
||||
int i = 0;
|
||||
final handler = FakeAwaitableDataSender<TestDataType, TestDataType>(
|
||||
onAddFunc: () {
|
||||
i++;
|
||||
expect(i <= 2, true);
|
||||
}
|
||||
);
|
||||
final a = handler.sendData(TestDataType("1"), id: "1");
|
||||
final b = handler.sendData(TestDataType("2"), id: "2");
|
||||
test('Queue multiple data packets and resolve in reverse order', () async {
|
||||
var i = 0;
|
||||
final handler = FakeAwaitableDataSender<TestDataType, TestDataType>(
|
||||
onAddFunc: () {
|
||||
i++;
|
||||
expect(i <= 2, true);
|
||||
},
|
||||
);
|
||||
final a = handler.sendData(TestDataType('1'), id: '1');
|
||||
final b = handler.sendData(TestDataType('2'), id: '2');
|
||||
|
||||
await handler.onData(DataWrapper("2", TestDataType("4")));
|
||||
await handler.onData(DataWrapper("1", TestDataType("1")));
|
||||
await handler.onData(DataWrapper('2', TestDataType('4')));
|
||||
await handler.onData(DataWrapper('1', TestDataType('1')));
|
||||
|
||||
expect((await a)!.data, "1");
|
||||
expect((await b)!.data, "4");
|
||||
expect((await a)!.data, '1');
|
||||
expect((await b)!.data, '4');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import "package:moxlib/math.dart";
|
||||
|
||||
import "package:test/test.dart";
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group("implies", () {
|
||||
test("Truth table test", () {
|
||||
expect(implies(true, true), true);
|
||||
expect(implies(true, false), false);
|
||||
expect(implies(false, true), true);
|
||||
expect(implies(false, false), true);
|
||||
});
|
||||
group('implies', () {
|
||||
test('Truth table test', () {
|
||||
expect(implies(true, true), true);
|
||||
expect(implies(true, false), false);
|
||||
expect(implies(false, true), true);
|
||||
expect(implies(false, false), true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user