From 386cf3b78a6288f3e905cdd9dfca33205850af2b Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Fri, 25 Oct 2024 15:58:27 +0000 Subject: [PATCH] check point --- developer/javac/IO.java | 139 ++++++++++---- .../javac/{TestBench.javax => TestBench.java} | 173 ++++++++++++------ developer/javac/Util.java | 24 ++- tester/javac/TestIO.java | 26 +-- 4 files changed, 257 insertions(+), 105 deletions(-) rename developer/javac/{TestBench.javax => TestBench.java} (65%) diff --git a/developer/javac/IO.java b/developer/javac/IO.java index da21e2d..4514377 100644 --- a/developer/javac/IO.java +++ b/developer/javac/IO.java @@ -1,63 +1,128 @@ package com.ReasoningTechnology.Mosaic; +/* + The primary purpose of this class is to redirect I/O to buffers, + sot that a test can check the I/O behavior of a function under test. +*/ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.FileDescriptor; import java.io.PrintStream; import java.io.InputStream; -public class IO { +public class IO{ - private PrintStream original_out; - private PrintStream original_err; - private InputStream original_in; + private PrintStream original_out; + private PrintStream original_err; + private InputStream original_in; - private ByteArrayOutputStream out_content; - private ByteArrayOutputStream err_content; - private ByteArrayInputStream in_content; + private ByteArrayOutputStream out_content; + private ByteArrayOutputStream err_content; + private ByteArrayInputStream in_content; + private boolean streams_foobar = false; + private boolean uninitialized = true; - public void redirect_io(String input_data){ - original_out = System.out; - original_err = System.err; - original_in = System.in; - out_content = new ByteArrayOutputStream(); - err_content = new ByteArrayOutputStream(); - in_content = new ByteArrayInputStream(input_data.getBytes()); + // IO currently has no constructors defined, uses default - System.setOut(new PrintStream(out_content)); - System.setErr(new PrintStream(err_content)); - System.setIn(in_content); - } - public void restore_io(){ - // Flush the output streams to prevent carrying over data - flush_buffers(); + // Redirects IO streams, logs and handles errors if redirection fails. + // + // Most tests do not do I/O checks, so rather than throwing an error + // it will set the streams_foobar flag, then throw an error if the I/O + // functions are used. + // + // This is the only method that can set the streams_foobar flag. + public boolean redirect(){ + + try{ + original_out = System.out; + original_err = System.err; + original_in = System.in; + + out_content = new ByteArrayOutputStream(); + err_content = new ByteArrayOutputStream(); + in_content = new ByteArrayInputStream(new byte[0]); + + System.setOut( new PrintStream(out_content) ); + System.setErr( new PrintStream(err_content) ); + System.setIn(in_content); + + uninitialized = false; + return true; + + } catch(Exception e){ + restore_hard(); + streams_foobar = true; + return false; - System.setOut(original_out); - System.setErr(original_err); - System.setIn(original_in); } + } - public void clear_buffers(){ - out_content.reset(); - err_content.reset(); + // Hard restore of the streams, resetting to system defaults + public void restore_hard(){ + System.setOut(new PrintStream( new FileOutputStream(FileDescriptor.out)) ); + System.setErr(new PrintStream( new FileOutputStream(FileDescriptor.err))) ; + System.setIn(new FileInputStream(FileDescriptor.in)); + } + + // Restores original IO streams, ensuring foobar and uninitialized states are checked. + // If anything goes wrong reverse to restore_hard. + public void restore(){ + if(unitialized || streams_foobar){ + restore_hard(); + return; + } + try{ + System.setOut(original_out); + System.setErr(original_err); + System.setIn(original_in); + } catch{Throwable e){ + restore_hard(); } + } - public void flush_buffers(){ - // Clear the buffers for the next use - out_content.reset(); - err_content.reset(); + // Clears output, error, and input buffers, checks for foobar state only. + public void clear_buffers(){ + if(streams_foobar){ + throw new IllegalStateException("Cannot clear buffers: IO object is in foobar state."); } + out_content.reset(); + err_content.reset(); + in_content = new ByteArrayInputStream( new byte[0] ); // Reset to EOF + System.setIn(in_content); + } - public ByteArrayInputStream get_in_content(){ - return in_content; + // Returns stdout content as a string, checks foobar state only. + public String get_out_content(){ + if(streams_foobar){ + throw new IllegalStateException + ( + "Cannot access stdout content: IO object is in foobar state." + ); } + return out_content.toString(); + } - public ByteArrayOutputStream get_out_content(){ - return out_content; + // Returns stderr content as a string, checks foobar state only. + public String get_err_content(){ + if(streams_foobar){ + throw new IllegalStateException + ( + "Cannot access stderr content: IO object is in foobar state." + ); } + return err_content.toString(); + } - public ByteArrayOutputStream get_err_content(){ - return err_content; + // Pushes input string onto stdin, checks foobar state only. + public void push_input(String input_data){ + if(streams_foobar){ + throw new IllegalStateException("Cannot push input: IO object is in foobar state."); } + in_content = new ByteArrayInputStream( input_data.getBytes() ); + System.setIn(in_content); + } } diff --git a/developer/javac/TestBench.javax b/developer/javac/TestBench.java similarity index 65% rename from developer/javac/TestBench.javax rename to developer/javac/TestBench.java index eb09d6e..5eb1ac2 100644 --- a/developer/javac/TestBench.javax +++ b/developer/javac/TestBench.java @@ -49,16 +49,96 @@ public class TestBench{ return true; } - - public static void flush_stdin() throws IOException{ - while(System.in.available() > 0){ - System.in.read(); + + public static boolean run_test(){ + String test_name = method.getName() + + + // Ways a test can fail ,not exclusive + boolean fail_malformed = false; + boolean fail_reported = false; + boolean fail_exception = false; + boolean fail_extraneous_stdout = false; + boolean fail_extraneous_stderr = false; + + if( !io.redirect() ){ + Util.log_message + ( + "Mosaic::TestBench::run redirect I/O failed before running test \'" + + test_name + "\', so most class IO methods will throw an uncaught error if called." + ); + Ssytem.out.println + ( + "Mosaic::TestBench::run Immediately before running test, \"" + + test + + "\' I/O redirect failed." + ); + }else{ + io.clear_buffers(); + } + + // the method_is_wellformed prints more specific messages than found here + if( !method_is_wellformed(method) ){ + System.out.println + ( + "Mosaic::TestBench::run test \'" + + test_name + + "\' has incorrect type signature for a TestBench test, calling it a failure." + ); + failed_test++; + continue; + } + + // Finally the gremlins run the test! + try{ + + Object result = method.invoke(test_suite ,in_content ,out_content ,err_content); + fail_reported = !Boolean.TRUE.equals(result); // test passes if ,and only if ,it returns exactly 'true'. + + // A test fails when there is extraneous output + fail_extraneous_stdout = out_content.size() > 0; + fail_extraneous_stderr = err_content.size() > 0; + + // We keep it to log it + if(fail_extraneous_stdout){ stdout_string = out_content.toString(); } + if(fail_extraneous_stderr){ stderr_string = err_content.toString(); } + + } catch(Exception e){ + + // A test fails when there is an unhandled exception. + fail_exception = true; + + // We keep it to report it + exception_string = e.toString(); + + } finally{ + + // Restore original stdin ,stdout ,and stderr + System.setOut(original_out); + System.setErr(original_err); + System.setIn(original_in); } - } - public static void set_test_input(String input_data){ - ByteArrayInputStream test_in = new ByteArrayInputStream(input_data.getBytes()); - System.setIn(test_in); + if(fail_reported) System.out.println("failed: \'" + test_name + "\' by report from test."); + if(fail_exception) System.out.println("failed: \'" + test_name + "\' due to unhandled exception: " + exception_string); + if(fail_extraneous_stdout){ + System.out.println("failed: \'" + test_name + "\' due extraneous stdout output ,see log."); + log_output(test_name ,"stdout" ,stdout_string); + } + if(fail_extraneous_stderr){ + System.out.println("failed: \'" + test_name + "\' due extraneous stderr output ,see log."); + log_output(test_name ,"stderr" ,stderr_string); + } + + boolean test_failed = + fail_reported + || fail_exception + || fail_extraneous_stdout + || fail_extraneous_stderr + ; + + return !test_failed; } @@ -68,63 +148,46 @@ public class TestBench{ int passed_test = 0; Method[] methods = test_suite.getClass().getDeclaredMethods(); + io = new IO(); for(Method method : methods){ // Ways a test can fail ,not exclusive - boolean fail_testbench = false; boolean fail_malformed = false; boolean fail_reported = false; boolean fail_exception = false; boolean fail_extraneous_stdout = false; boolean fail_extraneous_stderr = false; - if( !method_is_wellformed(method) ){ - // the malformed check prints specific messages - System.out.println("TestBench: malformed test counted as a failure:\'" + method.getName() + "\'"); - failed_test++; - continue; + if( !io.redirect() ){ + Util.log_message + ( + "Mosaic::TestBench::run redirect I/O failed before running test \'" + + test_name + "\', so most class IO methods will throw an uncaught error if called." + ); + Ssytem.out.println + ( + "Mosaic::TestBench::run Immediately before running test, \"" + + test + + "\' I/O redirect failed." + ); + }else{ + io.clear_buffers(); } - PrintStream original_out = null; - PrintStream original_err = null; - InputStream original_in = null; - - ByteArrayOutputStream out_content = null; - ByteArrayOutputStream err_content = null; - ByteArrayInputStream in_content = null; - - try{ - // Redirect the I/O channels so the tests can manipulate them as data. - original_out = System.out; - original_err = System.err; - original_in = System.in; - - out_content = new ByteArrayOutputStream(); - err_content = new ByteArrayOutputStream(); - in_content = new ByteArrayInputStream(); - - System.setOut(new PrintStream(out_content)); - System.setErr(new PrintStream(err_content)); - System.setIn(in_content); - - } catch(Throwable e){ // Catches both Errors and Exceptions - // Restore stdout ,stderr ,and stdin before reporting the error - System.setOut(original_out); - System.setErr(original_err); - System.setIn(original_in); - - // Report the error - System.out.println("TestBench:: when redirecting i/o in preparation for running test \'" + method.getName() + "\' ,test bench itself throws error: " + e.toString()); + // the method_is_wellformed prints more specific messages than found here + if( !method_is_wellformed(method) ){ + System.out.println + ( + "Mosaic::TestBench::run test \'" + + test_name + + "\' has incorrect type signature for a TestBench test, calling it a failure." + ); failed_test++; continue; } - // Capture detritus - String exception_string = ""; - String stdout_string = ""; - String stderr_string = ""; - // Finally the gremlins run the test! try{ @@ -165,15 +228,15 @@ public class TestBench{ failed_test++; - if(fail_reported) System.out.println("failed: \'" + method.getName() + "\' by report from test."); - if(fail_exception) System.out.println("failed: \'" + method.getName() + "\' due to unhandled exception: " + exception_string); + if(fail_reported) System.out.println("failed: \'" + test_name + "\' by report from test."); + if(fail_exception) System.out.println("failed: \'" + test_name + "\' due to unhandled exception: " + exception_string); if(fail_extraneous_stdout){ - System.out.println("failed: \'" + method.getName() + "\' due extraneous stdout output ,see log."); - log_output(method.getName() ,"stdout" ,stdout_string); + System.out.println("failed: \'" + test_name + "\' due extraneous stdout output ,see log."); + log_output(test_name ,"stdout" ,stdout_string); } if(fail_extraneous_stderr){ - System.out.println("failed: \'" + method.getName() + "\' due extraneous stderr output ,see log."); - log_output(method.getName() ,"stderr" ,stderr_string); + System.out.println("failed: \'" + test_name + "\' due extraneous stderr output ,see log."); + log_output(test_name ,"stderr" ,stderr_string); } } else{ diff --git a/developer/javac/Util.java b/developer/javac/Util.java index 13e2c14..e4d59c7 100644 --- a/developer/javac/Util.java +++ b/developer/javac/Util.java @@ -6,6 +6,9 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Method; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; public class Util{ @@ -25,18 +28,35 @@ public class Util{ for( boolean condition : conditions ) condition = true; } + public static String iso_utc_time(){ + return Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); + } - + // used to report if a test completed with data still on an output streams public static void log_output(String test_name ,String stream ,String output_data){ try(FileWriter log_writer = new FileWriter("test_log.txt" ,true)){ // Append mode + log_writer.write("\n" + iso_utc_time() + " -----------------------------------------------------------\n"); log_writer.write("Test: " + test_name + "\n"); log_writer.write("Stream: " + stream + "\n"); log_writer.write("Output:\n" + output_data + "\n"); - log_writer.write("----------------------------------------\n"); } catch(IOException e) { System.err.println("Error writing to log for test: " + test_name + ", stream: " + stream); e.printStackTrace(System.err); } } + // used to log a general message about a test + public static void log_message(String test_name ,String message){ + try(FileWriter log_writer = new FileWriter("test_log.txt" ,true)){ // Append mode + log_writer.write("\n" + iso_utc_time() + " -----------------------------------------------------------\n"); + log_writer.write("Test: " + test_name + "\n"); + log_writer.write("Message:\n" + message + "\n"); + } catch(IOException e) { + System.err.println("Error writing to log for test: " + test_name + ", stream: " + stream); + e.printStackTrace(System.err); + } + } + + + } diff --git a/tester/javac/TestIO.java b/tester/javac/TestIO.java index 1e0ecba..0b0ffbf 100644 --- a/tester/javac/TestIO.java +++ b/tester/javac/TestIO.java @@ -16,12 +16,12 @@ public class TestIO{ // Count remaining characters until EOF int count = 0; - while( System.in.read() != -1 ){ + while(System.in.read() != -1){ count++; } return count; - } catch( Exception e ){ + } catch(Exception e){ e.printStackTrace(); return -1; // Error case } @@ -29,30 +29,32 @@ public class TestIO{ public static int run(){ IO io = new IO(); - String input_data = "abcdefg"; // Sample input boolean[] condition = new boolean[3]; // Redirect IO streams - io.redirect_io(input_data); + io.redirect(); + + // Provide input for the function under test + io.push_input("abcdefg"); // Execute function under test int result = fut(); // Check stdout content - String stdout = io.get_out_content().toString(); - condition[0] = stdout.equals("ab"); + String stdout_string = io.get_out_content(); + condition[0] = stdout_string.equals("ab"); // Check stderr content - String stderr = io.get_err_content().toString(); - condition[1] = stderr.equals("cd"); + String stderr_string = io.get_err_content(); + condition[1] = stderr_string.equals("cd"); - // Check returned character count (3 remaining characters: 'e' ,'f' ,'g') + // Check returned character count (3 remaining characters: 'e','f','g') condition[2] = result == 3; // Restore original IO streams - io.restore_io(); + io.restore(); - if( !Util.all(condition) ){ + if(!Util.all(condition)){ System.out.println("TestIO failed"); return 1; } @@ -68,3 +70,5 @@ public class TestIO{ } } + + -- 2.20.1