Skip to content

Commit

Permalink
test: Attempt to fix flaky tests for Windows & Android (#1441)
Browse files Browse the repository at this point in the history
# Description

The tests in Windows & Android are possibly flaky for this reason:
- In general it is not tested, if a source starts to play for app tests.
- In the app test for `ReleaseMode.loop`, the source starts to play, but
never completes or stops. See:
https://github.com/bluefireteam/audioplayers/blob/fe81f3b1e600fe005febbe7cd3da02735a3de004/packages/audioplayers/example/integration_test/app/tabs/controls_tab.dart#L104
. This is now checked by getting the current position.

The solution to the problem also raises another issue on Android, as the
position can not be retrieved on very short sources, thus they are
excluded from these tests and it is expected to make it work with
Exoplayer (#1526).
  • Loading branch information
Gustl22 authored Sep 7, 2023
1 parent bd300cf commit c5b0b6e
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

import '../../platform_features.dart';
Expand All @@ -19,19 +19,19 @@ Future<void> testControlsTab(
await tester.pumpAndSettle();

// Sources take some time to get initialized
const timeout = Duration(seconds: 8);
const stopDuration = Duration(seconds: 5);

if (features.hasVolume) {
await tester.testVolume('0.5', timeout: timeout);
await tester.testVolume('0.0', timeout: timeout);
await tester.testVolume('1.0', timeout: timeout);
await tester.testVolume('0.5', stopDuration: stopDuration);
await tester.testVolume('0.0', stopDuration: stopDuration);
await tester.testVolume('1.0', stopDuration: stopDuration);
// No tests for volume > 1
}

if (features.hasBalance) {
await tester.testBalance('-1.0', timeout: timeout);
await tester.testBalance('1.0', timeout: timeout);
await tester.testBalance('0.0', timeout: timeout);
await tester.testBalance('-1.0', stopDuration: stopDuration);
await tester.testBalance('1.0', stopDuration: stopDuration);
await tester.testBalance('0.0', stopDuration: stopDuration);
}

if (features.hasPlaybackRate && !audioSourceTestData.isLiveStream) {
Expand All @@ -48,17 +48,15 @@ Future<void> testControlsTab(

// Linux cannot complete seek if duration is not present.
await tester.testSeek('0.5', isResume: false);
await tester.tap(find.byKey(const Key('streamsTab')));
await tester.pumpAndSettle();

if (isImmediateDurationSupported) {
await tester.testPosition(
Duration(seconds: audioSourceTestData.duration!.inSeconds ~/ 2),
matcher: greaterThanOrEqualTo,
);
}
await tester.tap(find.byKey(const Key('controlsTab')));
await tester.pumpAndSettle();
await tester.doInStreamsTab((tester) async {
if (isImmediateDurationSupported) {
await tester.testPosition(
Duration(seconds: audioSourceTestData.duration!.inSeconds ~/ 2),
matcher: (Object? value) =>
greaterThanOrEqualTo(value ?? Duration.zero),
);
}
});

await tester.pump(const Duration(seconds: 1));
await tester.testSeek('1.0');
Expand Down Expand Up @@ -100,9 +98,23 @@ Future<void> testControlsTab(

if (!audioSourceTestData.isLiveStream &&
audioSourceTestData.duration! < const Duration(seconds: 2)) {
if (features.hasReleaseModeLoop) {
final isAndroid =
!kIsWeb && defaultTargetPlatform == TargetPlatform.android;
// FIXME(gustl22): Android provides no position for samples shorter
// than 0.5 seconds.
if (features.hasReleaseModeLoop &&
!(isAndroid &&
audioSourceTestData.duration! < const Duration(seconds: 1))) {
await tester.testReleaseMode(ReleaseMode.loop);
await tester.pump(const Duration(seconds: 3));
// Check if sound has started playing.
await tester.doInStreamsTab((tester) async {
await tester.testPosition(
Duration.zero,
matcher: (Duration? position) =>
greaterThan(position ?? Duration.zero),
);
});
await tester.stop();
await tester.testReleaseMode(ReleaseMode.stop, isResume: false);
await tester.pumpAndSettle();
Expand All @@ -111,8 +123,11 @@ Future<void> testControlsTab(
if (features.hasReleaseModeRelease) {
await tester.testReleaseMode(ReleaseMode.release);
await tester.pump(const Duration(seconds: 3));
// No need to call stop, as it should be released by now
// TODO(Gustl22): test if source was released
// No need to call stop, as it should be released by now.
// Ensure source was released by checking `position == null`.
await tester.doInStreamsTab((tester) async {
await tester.testPosition(null);
});

// Reinitialize source
await tester.tap(find.byKey(const Key('sourcesTab')));
Expand Down Expand Up @@ -146,34 +161,34 @@ extension ControlsWidgetTester on WidgetTester {

Future<void> testVolume(
String volume, {
Duration timeout = const Duration(seconds: 1),
Duration stopDuration = const Duration(seconds: 1),
}) async {
printWithTimeOnFailure('Test Volume: $volume');
await scrollToAndTap(Key('control-volume-$volume'));
await resume();
await pump(timeout);
await pump(stopDuration);
await stop();
}

Future<void> testBalance(
String balance, {
Duration timeout = const Duration(seconds: 1),
Duration stopDuration = const Duration(seconds: 1),
}) async {
printWithTimeOnFailure('Test Balance: $balance');
await scrollToAndTap(Key('control-balance-$balance'));
await resume();
await pump(timeout);
await pump(stopDuration);
await stop();
}

Future<void> testRate(
String rate, {
Duration timeout = const Duration(seconds: 2),
Duration stopDuration = const Duration(seconds: 2),
}) async {
printWithTimeOnFailure('Test Rate: $rate');
await scrollToAndTap(Key('control-rate-$rate'));
await resume();
await pump(timeout);
await pump(stopDuration);
await stop();
}

Expand Down Expand Up @@ -223,4 +238,16 @@ extension ControlsWidgetTester on WidgetTester {
await resume();
}
}

Future<void> doInStreamsTab(
Future<void> Function(WidgetTester tester) foo,
) async {
await tap(find.byKey(const Key('streamsTab')));
await pump();

await foo(this);

await tap(find.byKey(const Key('controlsTab')));
await pump();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ extension PropertiesWidgetTester on WidgetTester {
}

Future<void> testPosition(
Duration position, {
Matcher Function(Duration) matcher = equals,
Duration? position, {
Matcher Function(Duration?) matcher = equals,
Duration timeout = const Duration(seconds: 4),
}) async {
printWithTimeOnFailure('Test Position: $position');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Future<void> testStreamsTab(
if (features.hasPositionEvent) {
await tester.testPosition(
Duration.zero,
matcher: greaterThan,
matcher: (Duration? position) => greaterThan(position ?? Duration.zero),
timeout: timeout,
);
await tester.testOnPosition(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

extension LibWidgetTester on WidgetTester {
Future<void> pumpLinux() async {
if (!kIsWeb && Platform.isLinux) {
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.linux) {
// FIXME(gustl22): Linux needs additional pump (#1556)
await pump();
}
Expand Down
4 changes: 1 addition & 3 deletions packages/audioplayers/example/integration_test/lib_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:io';

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
Expand All @@ -13,7 +11,7 @@ import 'test_utils.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final features = PlatformFeatures.instance();
final isAndroid = !kIsWeb && Platform.isAndroid;
final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
final audioTestDataList = await getAudioTestDataList();

group('play multiple sources', () {
Expand Down
60 changes: 36 additions & 24 deletions packages/audioplayers/example/integration_test/platform_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';

import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers_platform_interface/audioplayers_platform_interface.dart';
Expand All @@ -16,7 +15,8 @@ import 'test_utils.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final features = PlatformFeatures.instance();
final isLinux = !kIsWeb && Platform.isLinux;
final isLinux = !kIsWeb && defaultTargetPlatform == TargetPlatform.linux;
final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
final audioTestDataList = await getAudioTestDataList();

group('Platform method channel', () {
Expand Down Expand Up @@ -352,30 +352,42 @@ void main() async {
}

for (final td in audioTestDataList) {
if (features.hasPositionEvent &&
(td.isLiveStream || td.duration! > const Duration(seconds: 2))) {
testWidgets('#positionEvent ${td.source}', (tester) async {
await tester.prepareSource(
playerId: playerId,
platform: platform,
testData: td,
);
if (features.hasPositionEvent) {
testWidgets(
'#positionEvent ${td.source}',
(tester) async {
await tester.prepareSource(
playerId: playerId,
platform: platform,
testData: td,
);

final eventStream = platform.getEventStream(playerId);
Duration? position;
final onPositionSub = eventStream
.where((event) => event.eventType == AudioEventType.position)
.listen(
(event) => position = event.position,
);
final eventStream = platform.getEventStream(playerId);
Duration? position;
final onPositionSub = eventStream.where(
(event) {
return event.eventType == AudioEventType.position &&
event.position != null &&
event.position! > Duration.zero;
},
).listen(
(event) => position = event.position,
);

await platform.resume(playerId);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(position, greaterThan(Duration.zero));
await platform.stop(playerId);
await onPositionSub.cancel();
await tester.pumpLinux();
});
await platform.resume(playerId);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(position, isNotNull);
expect(position, greaterThan(Duration.zero));
await platform.stop(playerId);
await onPositionSub.cancel();
await tester.pumpLinux();
},
// FIXME(gustl22): Android provides no position for samples shorter
// than 0.5 seconds.
skip: isAndroid &&
!td.isLiveStream &&
td.duration! < const Duration(seconds: 1),
);
}
}

Expand Down

0 comments on commit c5b0b6e

Please sign in to comment.