Skip to content

Commit

Permalink
test: more reliable tests (#1343)
Browse files Browse the repository at this point in the history
* test: more reliable tests for live streams

* chore(workflow): run apt-get install non-interactively

* chore(workflow): install libunwind-dev on linux to work on GH runner image Ubuntu 22.04

* test: waitOneshot, wait for stop event

* test: wait for stop event in streams

* tests: simplify resume

* test: update release modes for platforms

* flutter format
  • Loading branch information
Gustl22 committed Dec 11, 2022
1 parent 414d94d commit 343822a
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 79 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/build.yaml
Expand Up @@ -202,9 +202,12 @@ jobs:
- name: Install Flutter requirements for Linux
run: |
sudo apt-get update
sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
- name: Install GStreamer
run: sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-good gstreamer1.0-plugins-bad
# Install libunwind-dev, see https://github.com/actions/runner-images/issues/6399#issuecomment-1285011525
run: |
sudo apt install -y libunwind-dev
sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-good gstreamer1.0-plugins-bad
- name: Example app - Build Linux app
working-directory: ./packages/audioplayers/example
run: |
Expand Down
2 changes: 1 addition & 1 deletion feature_parity_table.md
Expand Up @@ -48,7 +48,7 @@ Note: LLM means Low Latency Mode.
<tr><td>low latency mode</td><td>SDK >=21</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr>
<tr><td colspan="7"><strong>Audio Control Commands</strong></td></tr>
<tr><td>resume / pause / stop</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td></tr>
<tr><td>release</td><td>yes</td><td>not yet</td><td>yes</td><td>yes</td><td>yes</td><td>not yet</td></tr>
<tr><td>release</td><td>yes</td><td>yes (no mode)</td><td>yes (no mode)</td><td>yes (no mode)</td><td>not yet</td><td>not yet</td></tr>
<tr><td>loop</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td></tr>
<tr><td>volume</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td></tr>
<tr><td>seek</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td></tr>
Expand Down
Expand Up @@ -8,6 +8,7 @@ class PlatformFeatures {
hasBytesSource: false,
hasPlaylistSourceType: false,
hasLowLatency: false,
hasReleaseModeRelease: false,
hasDuckAudio: false,
hasRespectSilence: false,
hasStayAwake: false,
Expand All @@ -34,6 +35,7 @@ class PlatformFeatures {
hasBytesSource: false,
hasPlaylistSourceType: false,
hasLowLatency: false,
hasReleaseModeRelease: false,
hasDuckAudio: false,
hasRespectSilence: false,
hasStayAwake: false,
Expand All @@ -60,6 +62,7 @@ class PlatformFeatures {
hasBytesSource: false,
hasPlaylistSourceType: false,
hasLowLatency: false,
hasReleaseModeRelease: false,
hasDuckAudio: false,
hasRespectSilence: false,
hasStayAwake: false,
Expand Down
Expand Up @@ -17,17 +17,20 @@ Future<void> testControlsTab(
await tester.tap(find.byKey(const Key('controlsTab')));
await tester.pumpAndSettle();

// Live stream takes some time to get initialized
final timeout = Duration(seconds: audioSourceTestData.isLiveStream ? 8 : 1);

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

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

if (features.hasPlaybackRate && !audioSourceTestData.isLiveStream) {
Expand Down Expand Up @@ -59,8 +62,7 @@ Future<void> testControlsTab(
await tester.pump(const Duration(seconds: 1));
await tester.testSeek('1.0');
await tester.pump(const Duration(seconds: 1));
await tester.tap(find.byKey(const Key('control-stop')));
await tester.pumpAndSettle();
await tester.stop();
}

final isBytesSource = audioSourceTestData.sourceKey.contains('bytes');
Expand All @@ -70,14 +72,13 @@ Future<void> testControlsTab(
await tester.testPlayerMode(PlayerMode.lowLatency);

// Test resume
await tester.tap(find.byKey(const Key('control-resume')));
await tester.resume();
await tester.pump(const Duration(seconds: 1));
// Test pause
await tester.tap(find.byKey(const Key('control-pause')));
await tester.tap(find.byKey(const Key('control-resume')));
await tester.resume();
await tester.pump(const Duration(seconds: 1));
await tester.tap(find.byKey(const Key('control-stop')));
await tester.pumpAndSettle();
await tester.stop();

// Test volume
await tester.testVolume('0.5');
Expand All @@ -86,7 +87,7 @@ Future<void> testControlsTab(
// Test release mode: loop
await tester.testReleaseMode(ReleaseMode.loop);
await tester.pump(const Duration(seconds: 3));
await tester.tap(find.byKey(const Key('control-stop')));
await tester.stop();
await tester.testReleaseMode(ReleaseMode.stop, isResume: false);
await tester.pumpAndSettle();

Expand All @@ -100,7 +101,7 @@ Future<void> testControlsTab(
if (features.hasReleaseModeLoop) {
await tester.testReleaseMode(ReleaseMode.loop);
await tester.pump(const Duration(seconds: 3));
await tester.tap(find.byKey(const Key('control-stop')));
await tester.stop();
await tester.testReleaseMode(ReleaseMode.stop, isResume: false);
await tester.pumpAndSettle();
}
Expand All @@ -121,22 +122,36 @@ Future<void> testControlsTab(

await tester.testReleaseMode(ReleaseMode.stop, isResume: false);
await tester.pumpAndSettle();

// TODO(Gustl22): test 'control-release'
}
}
}

extension ControlsWidgetTester on WidgetTester {
Future<void> resume() async {
await tap(find.byKey(const Key('control-resume')));
await pumpAndSettle();
}

Future<void> stop() async {
final st = StackTrace.current.toString();

await tap(find.byKey(const Key('control-stop')));
await waitOneshot(const Key('toast-player-stopped-0'), stackTrace: st);
await pumpAndSettle();
}

Future<void> testVolume(
String volume, {
Duration timeout = const Duration(seconds: 1),
}) async {
printOnFailure('Test Volume: $volume');
await tap(find.byKey(Key('control-volume-$volume')));
await tap(find.byKey(const Key('control-resume')));
await resume();
// TODO(Gustl22): get volume from native implementation
await pump(timeout);
await tap(find.byKey(const Key('control-stop')));
await pumpAndSettle();
await stop();
}

Future<void> testBalance(
Expand All @@ -145,11 +160,10 @@ extension ControlsWidgetTester on WidgetTester {
}) async {
printOnFailure('Test Balance: $balance');
await tap(find.byKey(Key('control-balance-$balance')));
await tap(find.byKey(const Key('control-resume')));
await resume();
// TODO(novikov): get balance from native implementation
await pump(timeout);
await tap(find.byKey(const Key('control-stop')));
await pumpAndSettle();
await stop();
}

Future<void> testRate(
Expand All @@ -158,61 +172,56 @@ extension ControlsWidgetTester on WidgetTester {
}) async {
printOnFailure('Test Rate: $rate');
await tap(find.byKey(Key('control-rate-$rate')));
await tap(find.byKey(const Key('control-resume')));
await resume();
// TODO(Gustl22): get rate from native implementation
await pump(timeout);
await tap(find.byKey(const Key('control-stop')));
await pumpAndSettle();
await stop();
}

Future<void> testSeek(
String seek, {
bool isResume = true,
}) async {
printOnFailure('Test Seek: $seek');
final st = StackTrace.current.toString();

await tap(find.byKey(Key('control-seek-$seek')));

// Wait until appearance and disappearance
await waitFor(
() async => expect(
find.byKey(const Key('toast-seek-complete-0')),
findsOneWidget,
),
);
await waitFor(
() async => expect(
find.byKey(const Key('toast-seek-complete-0')),
findsNothing,
),
);
await waitOneshot(const Key('toast-seek-complete-0'), stackTrace: st);

if (isResume) {
await tap(find.byKey(const Key('control-resume')));
await resume();
}
}

Future<void> testPlayerMode(PlayerMode mode) async {
printOnFailure('Test Player Mode: ${mode.name}');
final st = StackTrace.current.toString();

await tap(find.byKey(Key('control-player-mode-${mode.name}')));
await waitFor(
() async => expectEnumToggleHasSelected(
const Key('control-player-mode'),
matcher: equals(mode),
),
stackTrace: st,
);
}

Future<void> testReleaseMode(ReleaseMode mode, {bool isResume = true}) async {
printOnFailure('Test Release Mode: ${mode.name}');
final st = StackTrace.current.toString();

await tap(find.byKey(Key('control-release-mode-${mode.name}')));
await waitFor(
() async => expectEnumToggleHasSelected(
const Key('control-release-mode'),
matcher: equals(mode),
),
stackTrace: st,
);
if (isResume) {
await tap(find.byKey(const Key('control-resume')));
await resume();
}
// TODO(Gustl22): get release mode from native implementation
}
Expand Down
Expand Up @@ -25,21 +25,6 @@ extension ControlsWidgetTester on WidgetTester {
await scrollTo(sourceWidgetKey);
await tap(find.byKey(sourceWidgetKey));

// Wait for toast appearance and disappearance
await waitFor(
() async => expect(
find.byKey(const Key('toast-source-set')),
findsOneWidget,
),
timeout: const Duration(seconds: 180),
stackTrace: st,
);
await waitFor(
() async => expect(
find.byKey(const Key('toast-source-set')),
findsNothing,
),
stackTrace: st,
);
await waitOneshot(const Key('toast-source-set'), stackTrace: st);
}
}

0 comments on commit 343822a

Please sign in to comment.