Skip to content

Commit

Permalink
Start using Monocle for JavaFX/TestFX unit testing and enable `headle…
Browse files Browse the repository at this point in the history
…ss` mode for the CI (#47)

* Use TestFX with Monocle for JavaFX unit testing and enable `headless` mode for the CI

* TestFreezeDetector now uses TestFX API to run unit tests

* Skip MediaRecorder tests on the CI

* Change assertion when we seek by clicking on the seek slider MediaPlayerTests

* Set up Virtual Display (for Linux)

* Set up Virtual Display (for Linux), new config

* Move Virtual Display config (for Linux) to `Test` step

* Remove `software` rendering option from `headless` mode

* Try Monocle version `21.0.2-jpms`

* Install `GStreamer and Plugins` (for Linux) and setup a Virtual Audio Device (for Linux)

* Revert Monocle version to `17.0.10-jpms`

* Install Media Features for Windows only and force JavaFX to use WMF backend

* List Windows Optional Features

* Enable `ServerMediaFoundation` Windows Feature

* Exclude `media-recorder` tests when running on CI

* Fix exclusion of `media-recorder` unit tests when running on CI

* Remove Windows Optional Features listing on the CI

* Move `media-recorder` junit tag on the class

* Set GStreamer as JavaFX Media backend for Windows on the CI

* Revert at using WMF as JavaFX Media backend for Windows on the CI
  • Loading branch information
besidev authored Oct 17, 2024
1 parent d83e7e3 commit 0a4de59
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 109 deletions.
51 changes: 45 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ name: CI build

on: [push]

env:
DISPLAY: ":99"
XVFB_PARAMS: "-screen 0 1920x1080x24"
RUNNING_ON_CI: true

jobs:
builds:
name: '${{ matrix.os }} with Java ${{ matrix.jdk }}'
runs-on: ${{ matrix.os }}
strategy:
matrix:
jdk: [17, 21, 23]
os: [ubuntu-latest, windows-latest] #, macos-13]
os: [ubuntu-latest, windows-latest]
fail-fast: false
max-parallel: 6
timeout-minutes: 30
Expand All @@ -24,19 +29,53 @@ jobs:
distribution: 'temurin'
java-version: ${{ matrix.jdk }}

- name: Install GStreamer and Plugins (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libgstreamer1.0-0 \
gstreamer1.0-libav \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly
- name: Set up Virtual Audio Device (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get install -y pulseaudio
pulseaudio --start --exit-idle-time=-1
pacmd load-module module-null-sink sink_name=DummyOutput
- name: Enable Media Features (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Enable-WindowsOptionalFeature -Online -FeatureName MediaPlayback -All
Enable-WindowsOptionalFeature -Online -FeatureName ServerMediaFoundation -All
- name: Set JavaFX Media Backend to WMF (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$env:JDK_JAVA_OPTIONS = "-Djfxmedia.platforms=Windows"
- name: Compile
run: |
./gradlew jar
./gradlew example:jar
- name: Set up Virtual Display (for Linux)
if: runner.os == 'Linux'
run: |
Xvfb ${{env.DISPLAY}} ${{env.XVFB_PARAMS}} &
- name: Test
run: |
if [[ "$RUNNER_OS" == "Linux" ]]; then
export DISPLAY=:99.0 && /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16;
fi
./gradlew -DciTest=true test
./gradlew test
shell: bash

- name: Javadoc
run: |
./gradlew javadoc
./gradlew javadoc
4 changes: 1 addition & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ configure(subprojects.findAll { it.name != 'example' }) {

dependencies {
testImplementation "org.junit.jupiter:junit-jupiter:$JUNIT_VERSION"
testImplementation "org.junit.jupiter:junit-jupiter-api:$JUNIT_VERSION"
testImplementation "org.junit.jupiter:junit-jupiter-params:$JUNIT_VERSION"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$JUNIT_VERSION"
testImplementation "org.junit.jupiter:junit-jupiter-engine:$JUNIT_VERSION"

testImplementation "de.sandec:JMemoryBuddy:$JMEMORYBUDDY_VERSION"
testImplementation "org.mockito:mockito-core:$MOCKITO_VERSION"
Expand Down
19 changes: 19 additions & 0 deletions freeze-detector/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
dependencies {
api "org.slf4j:slf4j-api:$SLF4J_API_VERSION"

testImplementation "one.jpro.platform.jpms:testfx-junit5:$TESTFX_VERSION"
testImplementation "one.jpro.platform.jpms:testfx-core:$TESTFX_VERSION"
testImplementation "one.jpro.platform.jpms:openjfx-monocle:$MONOCLE_VERSION"
}

test {
jvmArgs = [
"-Dtestfx.headless=true", "-Djava.awt.headless=true",
"--add-opens", "javafx.graphics/com.sun.glass.ui=org.testfx.core",
"--add-opens", "javafx.graphics/com.sun.javafx.application=org.testfx.core",
"--add-exports", "javafx.graphics/com.sun.javafx.application=org.testfx.core",
"--add-exports", "javafx.graphics/com.sun.glass.ui=org.testfx.monocle",
"--add-exports", "javafx.graphics/com.sun.javafx.util=org.testfx.monocle",
"--add-exports", "javafx.base/com.sun.javafx.logging=org.testfx.monocle"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import java.time.Duration;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class FreezeDetector {

Expand Down
16 changes: 16 additions & 0 deletions freeze-detector/src/test/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Module descriptor for the JPro Platform Freeze Detector Test module.
*
* @author Besmir Beqiri
*/
module one.jpro.platform.freezedetector.test {
requires one.jpro.platform.freezedetector;
requires org.slf4j;

requires org.junit.jupiter;
requires org.testfx.core;
requires org.testfx.junit5;
requires org.assertj.core;

opens one.jpro.platform.freezedetector.test;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package one.jpro.platform.freezedetector.test;

import one.jpro.platform.freezedetector.FreezeDetector;
import org.junit.jupiter.api.Test;
import org.testfx.framework.junit5.ApplicationTest;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests the freeze detector.
*
* @author Besmir Beqiri
*/
public class TestFreezeDetector extends ApplicationTest {

@Test
public void testFreezeDetector() throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
interact(() -> new FreezeDetector(Duration.ofMillis(100),
(thread, duration) -> counter.incrementAndGet()));

assertThat(counter.get()).isEqualTo(0);
Thread.sleep(200);
assertThat(counter.get()).isEqualTo(0);
interact(() -> {
try {
Thread.sleep(150);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
assertThat(counter.get()).isEqualTo(1);
Thread.sleep(200);
assertThat(counter.get()).isEqualTo(1);
interact(() -> {
try {
Thread.sleep(150);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
assertThat(counter.get()).isEqualTo(2);
}
}
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ JUNIT_VERSION = 5.11.2
ASSERTJ_VERSION = 3.26.3
HAMCREST_VERSION = 3.0
MOCKITO_VERSION = 5.14.1
TESTFX_VERSION = 4.0.18
TESTFX_VERSION = 4.0.18-jpms
MONOCLE_VERSION = 17.0.10-jpms
SLF4J_API_VERSION = 2.0.16
LOGBACK_VERSION = 1.5.10
28 changes: 24 additions & 4 deletions jpro-media/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ dependencies {
api "org.slf4j:slf4j-api:$SLF4J_API_VERSION"
api "org.json:json:$JSON_VERSION"

testImplementation "org.testfx:testfx-junit5:$TESTFX_VERSION"
testImplementation "one.jpro.platform.jpms:testfx-junit5:$TESTFX_VERSION"
testImplementation "one.jpro.platform.jpms:testfx-core:$TESTFX_VERSION"
testImplementation "one.jpro.platform.jpms:openjfx-monocle:$MONOCLE_VERSION"
testImplementation "org.bytedeco:javacv-platform:$JAVACV_VERSION"
testImplementation "org.bytedeco:javacpp-platform:$JAVACPP_VERSION"
testRuntimeOnly "org.bytedeco:flandmark-platform:1.07-1.5.8"
}

compileJava {
Expand All @@ -18,12 +22,28 @@ compileJava {

test {
jvmArgs = [
"-Dtestfx.headless=true", "-Djava.awt.headless=true",
"--add-opens", "javafx.graphics/com.sun.javafx.application=org.testfx.core",
"--add-opens", "javafx.graphics/com.sun.glass.ui=org.testfx.core",
"--add-exports", "javafx.base/com.sun.javafx.event=one.jpro.platform.media",
"--add-exports", "javafx.graphics/com.sun.javafx.application=testfx.core",
"--add-opens", "javafx.graphics/com.sun.javafx.application=testfx.core"
"--add-exports", "javafx.base/com.sun.javafx.logging=org.testfx.monocle",
"--add-exports", "javafx.graphics/com.sun.javafx.application=org.testfx.core",
"--add-exports", "javafx.graphics/com.sun.javafx.util=org.testfx.monocle",
"--add-exports", "javafx.graphics/com.sun.glass.ui=org.testfx.monocle"
]

onlyIf { !Boolean.getBoolean("ciTest") }
// Check if the RUNNING_ON_CI environment variable is set to 'true'
def runningOnCi = System.getenv('RUNNING_ON_CI') == 'true'

// Pass the environment variable to the test JVM as a system property
systemProperty 'RUNNING_ON_CI', runningOnCi

// Exclude media tests when running on CI
if (runningOnCi) {
useJUnitPlatform {
excludeTags('media-recorder')
}
}
}

javadoc {
Expand Down
9 changes: 2 additions & 7 deletions jpro-media/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import one.jpro.platform.media.MediaSource;
import one.jpro.platform.media.MediaView;
import one.jpro.platform.media.player.MediaPlayer;
import one.jpro.platform.media.recorder.MediaRecorder;

/**
* Defines APIs for playback and recording of video and audio content.
* <p>
Expand All @@ -17,8 +12,8 @@
* @author Besmir Beqiri
*/
module one.jpro.platform.media {
requires javafx.controls;
requires javafx.media;
requires transitive javafx.controls;
requires transitive javafx.media;
requires javafx.swing;
requires jpro.webapi;
requires org.json;
Expand Down
16 changes: 16 additions & 0 deletions jpro-media/src/test/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Module descriptor for the JPro Platform Media Test module.
*
* @author Besmir Beqiri
*/
open module one.jpro.platform.media.test {
requires one.jpro.platform.media;
requires org.slf4j;

requires org.junit.jupiter;
requires org.testfx.core;
requires org.testfx.junit5;
requires org.assertj.core;

exports one.jpro.platform.media.test;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package one.jpro.platform.media;
package one.jpro.platform.media.test;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
Expand All @@ -15,6 +15,8 @@
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
import one.jpro.platform.media.MediaSource;
import one.jpro.platform.media.MediaView;
import one.jpro.platform.media.player.MediaPlayer;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
Expand Down Expand Up @@ -147,6 +149,7 @@ private void start(Stage stage) {
scene = new Scene(rootPane, 800, 540);
stage.setScene(scene);
stage.show();
stage.toFront();
}

@Test
Expand Down Expand Up @@ -267,9 +270,9 @@ void seek_after_media_player_is_playing(FxRobot robot) throws TimeoutException {

log.debug("Seek to 360 seconds by clicking on seek slider");
final Duration seekTime = Duration.seconds(360);
seekViaRobot(robot, seekTime);
log.debug("Check current time is greater or equal to 360 seconds");
assertThat(mediaPlayer.getCurrentTime()).isGreaterThanOrEqualTo(seekTime);
seekViaRobot(robot, seekTime); // We can't expect this seek method to be precise
log.debug("Check if the difference between the requested seek time and the current one is less than 200 milliseconds");
assertThat(seekTime.subtract(mediaPlayer.getCurrentTime())).isLessThan(Duration.millis(200));
log.debug("Run additional checks...");
assertThat(playButton.isDisable()).isTrue();
assertThat(pauseButton.isDisable()).isFalse();
Expand Down
Loading

0 comments on commit 0a4de59

Please sign in to comment.