Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
* {@code main} when it has one, otherwise instantiates it (the first
* {@code Stage} creates the library's singleton {@code Window}), and
* implements the smoke-test switch {@code -Dscratch4j.ide.exitAfter=N}, which
* closes the window after N seconds so CI can run programs headlessly.
* closes the window after N seconds so CI can run programs headlessly. With
* the control channel enabled, that countdown starts after the first frame.
*
* <p>The class name is fixed so re-running does not litter the build folder;
* it lives in the default package like the student's classes.
Expand Down Expand Up @@ -45,6 +46,11 @@ public static void main(String[] args) throws Exception {
if (exitAfter > 0) {
Thread hook = new Thread(() -> {
try {
// Native graphics startup can take longer than the smoke duration.
// Give controlled runs their full duration after rendering starts.
if (Boolean.getBoolean("scratch4j.ide.control")) {
while (IdeControl.frames < 1) Thread.sleep(25);
}
Thread.sleep(exitAfter * 1000L);
Window.getInstance().exit();
} catch (InterruptedException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,50 @@ public enum TextureSampling { POINT }
root.resolve("classes")).success()).isTrue();
}

@Test
void controlledSmokeCountdownWaitsForTheFirstFrame() throws Exception {
Path launcher = root.resolve("Scratch4JLauncher.java");
Path main = root.resolve("Delayed.java");
Path window = root.resolve("org/openpatch/scratch/Window.java");
Path classes = root.resolve("classes");
Files.createDirectories(window.getParent());
Files.writeString(launcher, LauncherSource.generate());
Files.writeString(main, """
public class Delayed {
public static void main(String[] args) throws Exception {
Thread.sleep(2000);
System.out.println("FIRST_FRAME");
Scratch4JLauncher.IdeControl.frames = 1;
Thread.sleep(10000);
}
}
""");
Files.writeString(window, """
package org.openpatch.scratch;
public class Window {
public static Window getInstance() { return new Window(); }
public void setDebug(boolean value) {}
public Stage getStage() { return null; }
public void exit() { System.out.println("SMOKE_EXIT"); System.exit(0); }
}
""");
Path jar = LibraryJarSource.allJar(root.resolve("bundled"));
assertThat(new CompilerService().compile(List.of(launcher, main, window), List.of(jar), classes)
.errors()).isEmpty();
Process process = new ProcessBuilder(Path.of(System.getProperty("java.home"), "bin", "java").toString(),
"-Dscratch4j.ide.control=true", "-Dscratch4j.ide.exitAfter=1", "-cp",
classes + java.io.File.pathSeparator + jar, "Scratch4JLauncher", "Delayed")
.redirectErrorStream(true).start();
try {
assertThat(process.waitFor(15, java.util.concurrent.TimeUnit.SECONDS)).isTrue();
String output = new String(process.getInputStream().readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
assertThat(process.exitValue()).as(output).isZero();
assertThat(output).contains("FIRST_FRAME", "SMOKE_EXIT");
} finally {
process.destroyForcibly();
}
}

@Test
void launcherStartsJava25CompactSourceMain() throws Exception {
Path launcher = root.resolve("Scratch4JLauncher.java");
Expand Down
Loading