Fix Crash Bug in Android (ShareFile and Reconnect) + Force prettyDateString to use .toLocal() time + Formatting #794
|
@ -1 +1 @@
|
||||||
2023-09-26-13-15-v0.0.10
|
2024-01-03-20-52-v0.0.10-4-g6c0b2e2
|
|
@ -331,8 +331,9 @@ class MainActivity: FlutterActivity() {
|
||||||
val conversation: Int = call.argument("conversation") ?: 0
|
val conversation: Int = call.argument("conversation") ?: 0
|
||||||
val indexI: Int = call.argument("index") ?: 0
|
val indexI: Int = call.argument("index") ?: 0
|
||||||
val count: Int = call.argument("count") ?: 1
|
val count: Int = call.argument("count") ?: 1
|
||||||
|
val ucount : Int = maxOf(1, count) // don't allow negative counts
|
||||||
|
|
||||||
result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), count.toLong()))
|
result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), ucount.toLong()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
"SendMessage" -> {
|
"SendMessage" -> {
|
||||||
|
|
|
@ -156,4 +156,6 @@ abstract class Cwtch {
|
||||||
void DeleteServerInfo(String profile, String handle);
|
void DeleteServerInfo(String profile, String handle);
|
||||||
void PublishServerUpdate(String onion);
|
void PublishServerUpdate(String onion);
|
||||||
Future<void> ConfigureConnections(String onion, bool listen, bool peers, bool servers);
|
Future<void> ConfigureConnections(String onion, bool listen, bool peers, bool servers);
|
||||||
|
|
||||||
|
bool IsLoaded();
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,6 +268,8 @@ class CwtchFfi implements Cwtch {
|
||||||
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
EnvironmentConfig.debugLog("tearing down cwtch FFI isolate");
|
||||||
|
library.close();
|
||||||
cwtchIsolate.kill(priority: Isolate.immediate);
|
cwtchIsolate.kill(priority: Isolate.immediate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1121,4 +1123,11 @@ class CwtchFfi implements Cwtch {
|
||||||
PublishServerUpdate(utf8profile, utf8profile.length);
|
PublishServerUpdate(utf8profile, utf8profile.length);
|
||||||
malloc.free(utf8profile);
|
malloc.free(utf8profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool IsLoaded() {
|
||||||
|
bool check = library.providesSymbol("c_UpdateSettings");
|
||||||
|
EnvironmentConfig.debugLog("Checking that the FFI Interface is Correctly Loaded... $check");
|
||||||
|
return check;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,4 +469,9 @@ class CwtchGomobile implements Cwtch {
|
||||||
void PublishServerUpdate(String profile) {
|
void PublishServerUpdate(String profile) {
|
||||||
cwtchPlatform.invokeMethod("PublishServerUpdate", {"ProfileOnion": profile});
|
cwtchPlatform.invokeMethod("PublishServerUpdate", {"ProfileOnion": profile});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool IsLoaded() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,6 +173,10 @@ class FlwtchState extends State<Flwtch> with WindowListener {
|
||||||
getServerListStateProvider(),
|
getServerListStateProvider(),
|
||||||
],
|
],
|
||||||
builder: (context, widget) {
|
builder: (context, widget) {
|
||||||
|
// in test mode...rebuild everything every second...if cwtch isn't loaded...
|
||||||
|
if (EnvironmentConfig.TEST_MODE && cwtch.IsLoaded() == false) {
|
||||||
|
Timer t = new Timer.periodic(Duration(seconds: 1), (Timer t) => setState(() {}));
|
||||||
|
}
|
||||||
return Consumer2<Settings, AppState>(
|
return Consumer2<Settings, AppState>(
|
||||||
builder: (context, settings, appState, child) => MaterialApp(
|
builder: (context, settings, appState, child) => MaterialApp(
|
||||||
key: Key('app'),
|
key: Key('app'),
|
||||||
|
@ -190,7 +194,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
|
||||||
title: 'Cwtch',
|
title: 'Cwtch',
|
||||||
showSemanticsDebugger: settings.useSemanticDebugger,
|
showSemanticsDebugger: settings.useSemanticDebugger,
|
||||||
theme: mkThemeData(settings),
|
theme: mkThemeData(settings),
|
||||||
home: (!appState.cwtchInit || appState.modalState != ModalState.none) ? SplashView() : ProfileMgrView(),
|
home: (!appState.cwtchInit || appState.modalState != ModalState.none) || !cwtch.IsLoaded() ? SplashView() : ProfileMgrView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -75,6 +75,8 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
DateTime _lastRetryTime = DateTime.now();
|
DateTime _lastRetryTime = DateTime.now();
|
||||||
DateTime loaded = DateTime.now();
|
DateTime loaded = DateTime.now();
|
||||||
|
|
||||||
|
List<ContactEvent> contactEvents = List.empty(growable: true);
|
||||||
|
|
||||||
ContactInfoState(
|
ContactInfoState(
|
||||||
this.profileOnion,
|
this.profileOnion,
|
||||||
this.identifier,
|
this.identifier,
|
||||||
|
@ -198,6 +200,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
|
|
||||||
set status(String newVal) {
|
set status(String newVal) {
|
||||||
this._status = newVal;
|
this._status = newVal;
|
||||||
|
this.contactEvents.add(ContactEvent("Update Peer Status Received: $newVal"));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,3 +489,11 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ContactEvent {
|
||||||
|
String summary;
|
||||||
|
late DateTime timestamp;
|
||||||
|
ContactEvent(this.summary) {
|
||||||
|
this.timestamp = DateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ContactListState extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
//} </todo>
|
//} </todo>
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateLastMessageReceivedTime(int forIdentifier, DateTime newMessageTime) {
|
void updateLastMessageReceivedTime(int forIdentifier, DateTime newMessageTime) {
|
||||||
var contact = getContact(forIdentifier);
|
var contact = getContact(forIdentifier);
|
||||||
if (contact == null) return;
|
if (contact == null) return;
|
||||||
|
|
|
@ -30,22 +30,27 @@ class LocalIndexMessage {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.cacheOnly = cacheOnly;
|
this.cacheOnly = cacheOnly;
|
||||||
this.isLoading = isLoading;
|
this.isLoading = isLoading;
|
||||||
if (isLoading) {
|
loader = Completer<void>();
|
||||||
loader = Completer<void>();
|
loaded = loader.future;
|
||||||
loaded = loader.future;
|
if (!isLoading) {
|
||||||
|
loader.complete(); // complete this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void finishLoad(int messageId) {
|
void finishLoad(int messageId) {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
isLoading = false;
|
if (!loader.isCompleted) {
|
||||||
loader.complete(true);
|
isLoading = false;
|
||||||
|
loader.complete(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void failLoad() {
|
void failLoad() {
|
||||||
this.messageId = null;
|
this.messageId = null;
|
||||||
isLoading = false;
|
if (!loader.isCompleted) {
|
||||||
loader.complete(true);
|
isLoading = false;
|
||||||
|
loader.complete(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> waitForLoad() {
|
Future<void> waitForLoad() {
|
||||||
|
@ -95,7 +100,7 @@ class MessageCache extends ChangeNotifier {
|
||||||
this._storageMessageCount = newval;
|
this._storageMessageCount = newval;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On android reconnect, if backend supplied message count > UI message count, add the differnce to the front of the index
|
// On android reconnect, if backend supplied message count > UI message count, add the difference to the front of the index
|
||||||
void addFrontIndexGap(int count) {
|
void addFrontIndexGap(int count) {
|
||||||
this._indexUnsynced = count;
|
this._indexUnsynced = count;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:cwtch/config.dart';
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/models/remoteserver.dart';
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
|
@ -248,8 +249,22 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
if (profileContact != null) {
|
if (profileContact != null) {
|
||||||
profileContact.status = contact["status"];
|
profileContact.status = contact["status"];
|
||||||
|
|
||||||
var newCount = contact["numMessages"];
|
var newCount = contact["numMessages"] as int;
|
||||||
if (newCount != profileContact.totalMessages) {
|
if (newCount != profileContact.totalMessages) {
|
||||||
|
if (newCount < profileContact.totalMessages) {
|
||||||
|
// on Android, when sharing a file the UI may be briefly unloaded for the
|
||||||
|
// OS to display the file management/selection screen. Afterwards a
|
||||||
|
// call to ReconnectCwtchForeground will be made which will refresh all values (including count of numMessages)
|
||||||
|
// **at the same time** the foreground will increment .totalMessages and send a new message to the backend.
|
||||||
|
// This will result in a negative number of messages being calculated here, and an incorrect totalMessage count.
|
||||||
|
// This bug is exacerbated in debug mode, and when multiple files are sent in succession. Both cases result in multiple ReconnectCwtchForeground
|
||||||
|
// events that have the potential to conflict with currentMessageCounts.
|
||||||
|
// Note that *if* a new message came in at the same time, we would be unable to distinguish this case - as such this is specific instance of a more general problem
|
||||||
|
// TODO: A true-fix to this bug is to implement a syncing step in the foreground where totalMessages and inFlightMessages can be distinguished
|
||||||
|
// This requires a change to the backend to confirm submission of an inFlightMessage, which will be implemented in #664
|
||||||
|
EnvironmentConfig.debugLog("Conflicting message counts: $newCount ${profileContact.totalMessages}");
|
||||||
|
newCount = max(newCount, profileContact.totalMessages);
|
||||||
|
}
|
||||||
profileContact.messageCache.addFrontIndexGap(newCount - profileContact.totalMessages);
|
profileContact.messageCache.addFrontIndexGap(newCount - profileContact.totalMessages);
|
||||||
}
|
}
|
||||||
profileContact.totalMessages = newCount;
|
profileContact.totalMessages = newCount;
|
||||||
|
|
|
@ -39,5 +39,5 @@ String prettyDateString(BuildContext context, DateTime date) {
|
||||||
// }
|
// }
|
||||||
return AppLocalizations.of(context)!.now;
|
return AppLocalizations.of(context)!.now;
|
||||||
}
|
}
|
||||||
return DateFormat.yMd(Platform.localeName).add_jm().format(date);
|
return DateFormat.yMd(Platform.localeName).add_jm().format(date.toLocal());
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ LoadAssetThemes() async {
|
||||||
themes = await loadYamlThemes();
|
themes = await loadYamlThemes();
|
||||||
}
|
}
|
||||||
|
|
||||||
OpaqueThemeType getTheme(String themeId, String mode) {
|
OpaqueThemeType getTheme(String themeId, String mode) {
|
||||||
if (themeId == "") {
|
if (themeId == "") {
|
||||||
themeId = cwtch_theme;
|
themeId = cwtch_theme;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future<Map<String, Map<String, OpaqueThemeType>>> loadYamlThemes() async {
|
Future<Map<String, Map<String, OpaqueThemeType>>> loadYamlThemes() async {
|
||||||
final manifestJson = await rootBundle.loadString('AssetManifest.json');
|
final manifestJson = await rootBundle.loadString('AssetManifest.json');
|
||||||
final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes'));
|
final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes'));
|
||||||
|
@ -17,7 +15,7 @@ Future<Map<String, Map<String, OpaqueThemeType>>> loadYamlThemes() async {
|
||||||
Map<String, Map<String, OpaqueThemeType>> themes = Map();
|
Map<String, Map<String, OpaqueThemeType>> themes = Map();
|
||||||
|
|
||||||
for (String themefile in themesList) {
|
for (String themefile in themesList) {
|
||||||
if (themefile.substring(themefile.length-4) != ".yml") {
|
if (themefile.substring(themefile.length - 4) != ".yml") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,16 +75,16 @@ class YmlTheme extends OpaqueThemeType {
|
||||||
|
|
||||||
Color? getColor(String name) {
|
Color? getColor(String name) {
|
||||||
var val = yml["themes"][mode]["theme"][name];
|
var val = yml["themes"][mode]["theme"][name];
|
||||||
if (! (val is int)) {
|
if (!(val is int)) {
|
||||||
val = yml["themes"][mode]["theme"][val] ?? val;
|
val = yml["themes"][mode]["theme"][val] ?? val;
|
||||||
}
|
}
|
||||||
if (! (val is int)) {
|
if (!(val is int)) {
|
||||||
val = yml["themes"][mode]?["colors"][val] ?? val;
|
val = yml["themes"][mode]?["colors"][val] ?? val;
|
||||||
}
|
}
|
||||||
if (! (val is int)) {
|
if (!(val is int)) {
|
||||||
val = yml["colors"]?[val];
|
val = yml["colors"]?[val];
|
||||||
}
|
}
|
||||||
if (! (val is int)) {
|
if (!(val is int)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Color(0xFF000000 + val as int);
|
return Color(0xFF000000 + val as int);
|
||||||
|
@ -95,7 +93,7 @@ class YmlTheme extends OpaqueThemeType {
|
||||||
String? getImage(String name) {
|
String? getImage(String name) {
|
||||||
var val = yml["themes"][mode]["theme"]?[name];
|
var val = yml["themes"][mode]["theme"]?[name];
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
return path.join("assets", "themes", yml["themes"]["name"], val);
|
return path.join("assets", "themes", yml["themes"]["name"], val);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -137,4 +135,4 @@ class YmlTheme extends OpaqueThemeType {
|
||||||
// Images
|
// Images
|
||||||
|
|
||||||
get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage;
|
get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSettingsList() {
|
Widget _buildSettingsList() {
|
||||||
return Consumer<Settings>(builder: (context, settings, child) {
|
return Consumer<Settings>(builder: (ccontext, settings, child) {
|
||||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
|
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
|
||||||
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor);
|
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor);
|
||||||
return Scrollbar(
|
return Scrollbar(
|
||||||
|
|
|
@ -125,6 +125,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
}
|
}
|
||||||
// reset the disconnect button to allow for immediate connection...
|
// reset the disconnect button to allow for immediate connection...
|
||||||
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now().subtract(Duration(minutes: 2));
|
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now().subtract(Duration(minutes: 2));
|
||||||
|
Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Disconnect from Peer"));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/appstate.dart';
|
import 'package:cwtch/models/appstate.dart';
|
||||||
import 'package:cwtch/models/contact.dart';
|
import 'package:cwtch/models/contact.dart';
|
||||||
|
@ -84,6 +85,13 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> evWidgets = Provider.of<ContactInfoState>(context, listen: false).contactEvents.map((ev) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(ev.summary, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
|
||||||
|
subtitle: Text(ev.timestamp.toLocal().toIso8601String(), style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
return Scrollbar(
|
return Scrollbar(
|
||||||
trackVisibility: true,
|
trackVisibility: true,
|
||||||
controller: peerSettingsScrollController,
|
controller: peerSettingsScrollController,
|
||||||
|
@ -319,8 +327,14 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
||||||
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
|
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
])
|
]),
|
||||||
])
|
]),
|
||||||
|
Visibility(
|
||||||
|
visible: EnvironmentConfig.BUILD_VER == dev_version,
|
||||||
|
maintainSize: false,
|
||||||
|
child: Column(
|
||||||
|
children: evWidgets,
|
||||||
|
))
|
||||||
])))));
|
])))));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,6 +75,7 @@ class _MessageListState extends State<MessageList> {
|
||||||
.AttemptReconnection(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).onion);
|
.AttemptReconnection(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).onion);
|
||||||
}
|
}
|
||||||
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now();
|
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now();
|
||||||
|
Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Actively Retried Connection"));
|
||||||
setState(() {
|
setState(() {
|
||||||
// force update of this view...otherwise the button won't be removed fast enough...
|
// force update of this view...otherwise the button won't be removed fast enough...
|
||||||
});
|
});
|
||||||
|
@ -116,8 +117,8 @@ class _MessageListState extends State<MessageList> {
|
||||||
// Only show broken heart is the contact is offline...
|
// Only show broken heart is the contact is offline...
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
image: Provider.of<ContactInfoState>(outerContext).isOnline()
|
||||||
? (Provider.of<Settings>(context).theme.chatImage != null) ?
|
? (Provider.of<Settings>(context).theme.chatImage != null)
|
||||||
DecorationImage(
|
? DecorationImage(
|
||||||
repeat: ImageRepeat.repeat,
|
repeat: ImageRepeat.repeat,
|
||||||
image: AssetImage(Provider.of<Settings>(context).theme.chatImage),
|
image: AssetImage(Provider.of<Settings>(context).theme.chatImage),
|
||||||
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn))
|
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn))
|
||||||
|
|
12
run-tests.sh
12
run-tests.sh
|
@ -5,11 +5,11 @@ sed "s|featurePaths: REPLACED_BY_SCRIPT|featurePaths: <String>[$paths]|" integra
|
||||||
flutter pub run build_runner clean
|
flutter pub run build_runner clean
|
||||||
flutter pub run build_runner build --delete-conflicting-outputs
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
PATH=$PATH:$PWD/linux/Tor
|
PATH=$PATH:"$PWD/linux/Tor"
|
||||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"$PWD/linux/":"$PWD/linux/Tor/"
|
LD_LIBRARY_PATH="$PWD/linux/":"$PWD/linux/Tor/":$LD_LIBRARY_PATH
|
||||||
PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH LOG_FILE=test.log CWTCH_HOME=$PWD/integration_test/env/temp/ flutter test -d linux --dart-define TEST_MODE=true integration_test/gherkin_suite_test.dart
|
env PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH LOG_FILE=test.log CWTCH_HOME=$PWD/integration_test/env/temp/ flutter test -d linux --dart-define TEST_MODE=true integration_test/gherkin_suite_test.dart
|
||||||
#node index2.js
|
# node index2.js
|
||||||
#if [ "$HEADLESS" = "false" ]; then
|
# if [ "$HEADLESS" = "false" ]; then
|
||||||
# xdg-open integration_test/gherkin/reports/cucumber_report.html
|
# xdg-open integration_test/gherkin/reports/cucumber_report.html
|
||||||
#fi
|
# fi
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue