summaryrefslogtreecommitdiff
path: root/chapter25/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25/src/test')
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java27
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java69
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestC.java62
3 files changed, 134 insertions, 24 deletions
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index 2966ec04..d2357ef1 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -8,6 +8,9 @@ import com.seaofnodes.simple.util.Ary;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
@@ -235,21 +238,39 @@ int v0=0^0;
}
@Test
+ public void testConstantClassInitialization() throws IOException {
+ TestC.runSF("val x = 42; return 0;","constantClassInt","",0);
+ TestC.runSF("val f = { -> 7; }; return 0;","constantClassFunction","",0);
+ }
+
+ @Test
public void testHelloWorld() throws IOException {
String expected = "Hello, World!\n";
String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";";
- TestC.run(prog,"helloWorld",new Ary<>(new String[]{SYS_BLDDIR}),
+ // tests_raw1 concurrently builds Chapter22Test's helloWorld executable.
+ TestC.run(prog,"helloWorldSys",new Ary<>(new String[]{SYS_BLDDIR}),
TestC.CALL_CONVENTION, null, null, expected,0);
}
@Test
public void testHelloWorldDriver() throws Exception {
- Simple.main(new String[]{"-L",SYS_BLDDIR,"--norun","docs/examples/A_helloWorld.smp"});
+ String out = "build/objs/helloWorldDriver/";
+ Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","docs/examples/A_helloWorld.smp"});
+ assertTrue(Files.isRegularFile(Path.of(out,"A_helloWorld.o")));
}
@Test
public void testHelloWorldDriverLibFile() throws Exception {
- Simple.main(new String[]{"-L",SYS_FILE.toString(),"docs/examples/A_helloWorld.smp"});
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ PrintStream saved = System.out;
+ try( PrintStream capture = new PrintStream(output,true,StandardCharsets.UTF_8) ) {
+ System.setOut(capture);
+ Simple.main(new String[]{"-L",SYS_FILE.toString(),"-o","build/objs/helloWorldDriverLibFile/",
+ "docs/examples/A_helloWorld.smp"});
+ } finally {
+ System.setOut(saved);
+ }
+ assertEquals("Hello, World!\n",output.toString(StandardCharsets.UTF_8));
}
@Test
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java b/chapter25/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
new file mode 100644
index 00000000..f3f65183
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
@@ -0,0 +1,69 @@
+package com.seaofnodes.simple;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+public class NativeExecutionTest {
+ // A subprocess with deterministic stdout, stderr, and process status.
+ public static class Child {
+ public static void main(String[] args) {
+ System.out.println("before exit");
+ System.err.println("child diagnostic");
+ System.exit(Integer.parseInt(args[0]));
+ }
+ }
+
+ private static String child(int exit) throws IOException {
+ String java = Path.of(System.getProperty("java.home"),"bin","java").toString();
+ return TestC.exec(java,"-cp",System.getProperty("java.class.path"),
+ Child.class.getName(),Integer.toString(exit));
+ }
+
+ private static void checkFailure(int exit) throws IOException {
+ try {
+ child(exit);
+ fail("Accepted process exit "+exit);
+ } catch( IOException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("exit code: "+exit));
+ assertTrue(e.getMessage(),e.getMessage().contains("before exit"));
+ assertTrue(e.getMessage(),e.getMessage().contains("child diagnostic"));
+ }
+ }
+
+ @Test public void testNonzeroExit() throws IOException { checkFailure(7); }
+
+ @Test public void testFullWindowsExitCode() throws IOException {
+ assumeTrue(TestC.OS.startsWith("Windows"));
+ checkFailure(256); // Narrowing to a byte incorrectly reports success.
+ checkFailure(2816); // Observed Cygwin status for the Hello World crash.
+ }
+
+ @Test public void testSuccessfulOutput() throws IOException {
+ assertEquals("before exit"+System.lineSeparator(),child(0));
+ }
+
+ @Test public void testHelloWorldArtifacts() throws IOException {
+ new Chapter22Test().testHelloWorld();
+ new Chapter25Test().testHelloWorld();
+ // Model a parallel run paused between Chapter 22's link and execution.
+ // Chapter 25 must not replace the executable Chapter 22 is about to run.
+ String exe = "build/objs/helloWorld"+(TestC.OS.startsWith("Windows") ? ".exe" : "");
+ assertEquals("Hello, World!",TestC.exec(exe));
+ }
+
+ @Test public void testDriverNonzeroExit() throws Exception {
+ Path dir = Files.createTempDirectory(Path.of("build/objs"),"driver-exit-");
+ Path src = dir.resolve("fail.smp");
+ Files.writeString(src,"return 7;");
+ try {
+ Simple.main(new String[]{src.toString()});
+ fail("Driver accepted a failed executable");
+ } catch( IOException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("exit code: 7"));
+ }
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
index d9595282..55c62c37 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
@@ -138,7 +138,10 @@ public abstract class TestC {
public static String gcc( String main, double ignore, String... objs ) throws IOException {
String BLDDIR = "build/objs/test0/";
String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : "");
- return gcc(objs[0],null,null,false,exe);
+ linkExe(objs[0],null,null,null,exe);
+ // Module tests intentionally use the program exit code as their result.
+ ExecResult result = execute(null,exe);
+ return result.exit==0 ? result.out : "exec exit code: "+result.exit;
}
// Link with gcc, and execute the resulting binary, returning stdout as a
@@ -209,29 +212,46 @@ public abstract class TestC {
}
private static String execStdin( String stdin, String... args ) throws IOException {
+ ExecResult result = execute(stdin,args);
+ if( result.exit!=0 )
+ throw new IOException(String.join(" ",args)+"\nexec exit code: "+result.exit+
+ "\nstdout:\n"+result.out+"\nstderr:\n"+result.err);
+ return result.out;
+ }
+
+ private record ExecResult(int exit, String out, String err) {}
+
+ private static ExecResult execute( String stdin, String... args ) throws IOException {
// Execute results
ProcessBuilder smp = new ProcessBuilder(args);
- File stdinFile = null;
- File stdoutFile = File.createTempFile("simple-stdout",".txt");
- smp.redirectOutput(stdoutFile);
- if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
- else if( stdin!=null ) {
- stdinFile = File.createTempFile("simple-stdin",".txt");
- Files.writeString(stdinFile.toPath(),stdin);
- smp.redirectInput(stdinFile);
+ Path stdinFile = null;
+ Path stdoutFile = Files.createTempFile("simple-stdout",".txt");
+ Path stderrFile = Files.createTempFile("simple-stderr",".txt");
+ Process p = null;
+ try {
+ smp.redirectOutput(stdoutFile.toFile());
+ smp.redirectError(stderrFile.toFile());
+ if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ else if( stdin!=null ) {
+ stdinFile = Files.createTempFile("simple-stdin",".txt");
+ Files.writeString(stdinFile,stdin);
+ smp.redirectInput(stdinFile.toFile());
+ }
+ p = smp.start();
+ if( stdin==null )
+ p.getOutputStream().close();
+ // Windows statuses have 32 bits; narrowing can turn a failure into 0.
+ int exit = p.waitFor();
+ return new ExecResult(exit,Files.readString(stdoutFile),Files.readString(stderrFile));
+ } catch( InterruptedException e ) {
+ if( p!=null ) p.destroyForcibly();
+ Thread.currentThread().interrupt();
+ throw new IOException("interrupted",e);
+ } finally {
+ if( stdinFile!=null ) Files.deleteIfExists(stdinFile);
+ Files.deleteIfExists(stdoutFile);
+ Files.deleteIfExists(stderrFile);
}
- Process p = smp.start();
- if( stdin==null )
- p.getOutputStream().close();
- int exit;
- try { exit = (byte)p.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
- String result = Files.readString(stdoutFile.toPath());
- if( stdinFile != null )
- stdinFile.delete();
- stdoutFile.delete();
- if( exit!=0 )
- return "exec exit code: "+exit;
- return result;
}
static Ary<String> linkObjs(Ary<String> externPaths) {