using System; using System.Text; using Community.CsharpSqlite; using tcl.lang; using ClientData = System.Object; using Tcl_Interp = tcl.lang.Interp; using Tcl_Obj = tcl.lang.TclObject; using System.IO; using System.Diagnostics; class Testing { /* ** 2009 July 17 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code to implement the "sqlite" test harness ** which runs TCL commands for testing the C#-SQLite port. ** ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart ** C#-SQLite is an independent reimplementation of the SQLite software library ** ************************************************************************* */ public static void Main( string[] args ) { // Array of command-line argument strings. { string fileName = null; // Create the interpreter. This will also create the built-in // Tcl commands. Interp interp = new Interp(); // Make command-line arguments available in the Tcl variables "argc" // and "argv". If the first argument doesn't start with a "-" then // strip it off and use it as the name of a script file to process. // We also set the argv0 and TCL.Tcl_interactive vars here. if ( ( args.Length > 0 ) && !( args[0].StartsWith( "-" ) ) ) { fileName = args[0]; } TclObject argv = TclList.newInstance(); argv.preserve(); try { int i = 0; int argc = args.Length; if ( (System.Object)fileName == null ) { interp.setVar( "argv0", "tcl.lang.Shell", TCL.VarFlag.GLOBAL_ONLY ); interp.setVar( "tcl_interactive", "1", TCL.VarFlag.GLOBAL_ONLY ); } else { interp.setVar( "argv0", fileName, TCL.VarFlag.GLOBAL_ONLY ); interp.setVar( "tcl_interactive", "0", TCL.VarFlag.GLOBAL_ONLY ); i++; argc--; } for ( ; i < args.Length; i++ ) { TclList.append( interp, argv, TclString.newInstance( args[i] ) ); } interp.setVar( "argv", argv, TCL.VarFlag.GLOBAL_ONLY ); interp.setVar( "argc", System.Convert.ToString( argc ), TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { throw new TclRuntimeError( "unexpected TclException: " + e.Message ); } finally { argv.release(); } init_all( interp ); TCL.Tcl_CreateObjCommand( interp, "load_testfixture_extensions", init_all_cmd, 0, null ); // Normally we would do application specific initialization here. // However, that feature is not currently supported. // If a script file was specified then just source that file // and quit. Console.WriteLine( " C#-SQLite version " + Sqlite3.sqlite3_version ); Console.WriteLine( "An independent reimplementation of the SQLite software library" ); Console.WriteLine( "==============================================================" ); Console.WriteLine( "" ); if ( (System.Object)fileName != null ) { try { interp.evalFile( fileName ); } catch ( TclException e ) { TCL.CompletionCode code = e.getCompletionCode(); if ( code == TCL.CompletionCode.RETURN ) { code = interp.updateReturnInfo(); if ( code != TCL.CompletionCode.OK ) { System.Console.Error.WriteLine( "command returned bad code: " + code ); if ( tcl.lang.ConsoleThread.debug ) System.Diagnostics.Debug.WriteLine( "command returned bad code: " + code ); } } else if ( code == TCL.CompletionCode.ERROR ) { System.Console.Error.WriteLine( interp.getResult().ToString() ); if ( tcl.lang.ConsoleThread.debug ) System.Diagnostics.Debug.WriteLine( interp.getResult().ToString() ); System.Diagnostics.Debug.Assert( false, interp.getResult().ToString() ); } else if ( code != TCL.CompletionCode.EXIT ) { System.Console.Error.WriteLine( "command returned bad code: " + code ); if ( tcl.lang.ConsoleThread.debug ) System.Diagnostics.Debug.WriteLine( "command returned bad code: " + code ); } } // Note that if the above interp.evalFile() returns the main // thread will exit. This may bring down the VM and stop // the execution of Tcl. // // If the script needs to handle events, it must call // vwait or do something similar. // // Note that the script can create AWT widgets. This will // start an AWT event handling thread and keep the VM up. However, // the interpreter thread (the same as the main thread) would // have exited and no Tcl scripts can be executed. interp.dispose(); Sqlite3.sqlite3_shutdown(); System.Environment.Exit( 0 ); } if ( (System.Object)fileName == null ) { // We are running in interactive mode. Start the ConsoleThread // that loops, grabbing stdin and passing it to the interp. ConsoleThread consoleThread = new ConsoleThread( interp ); consoleThread.IsBackground = true; consoleThread.Start(); // Loop forever to handle user input events in the command line. Notifier notifier = interp.getNotifier(); while ( true ) { // process events until "exit" is called. notifier.doOneEvent( TCL.ALL_EVENTS ); } } } } #if SQLITE_TEST //static void init_all(Tcl_Interp ); static int init_all_cmd( ClientData cd, Tcl_Interp interp, int objc, Tcl_Obj[] objv ) { Tcl_Interp slave; if ( objc != 2 ) { TCL.Tcl_WrongNumArgs( interp, 1, objv, "SLAVE" ); return TCL.TCL_ERROR; } slave = TCL.Tcl_GetSlave( interp, TCL.Tcl_GetString( objv[1] ) ); if ( slave == null ) { return TCL.TCL_ERROR; } init_all( slave ); return TCL.TCL_OK; } #endif // Setup SQLIte specific object for routines static void init_all( Interp interp ) { Sqlite3.Sqlite3_Init( interp ); Sqlite3.Sqlitetestbackup_Init( interp ); Sqlite3.Sqlitetestfuzzer_Init( interp ); Sqlite3.Sqliteconfig_Init( interp ); Sqlite3.Sqlitetest_autoext_Init( interp ); Sqlite3.Sqlitetest_func_Init( interp ); Sqlite3.Sqlitetest_hexio_Init( interp ); Sqlite3.Sqlitetest_malloc_Init( interp ); Sqlite3.Sqlitetest_mutex_Init( interp ); Sqlite3.Sqlitetestintarray_Init( interp ); Sqlite3.Sqlitetestschema_Init( interp ); Sqlite3.SqlitetestStat_Init( interp ); Sqlite3.Sqlitetesttclvar_Init( interp ); Sqlite3.Sqlitetestwholenumber_Init( interp ); Sqlite3.Sqlitetest1_Init( interp ); Sqlite3.Sqlitetest2_Init( interp ); Sqlite3.Sqlitetest3_Init( interp ); Sqlite3.Sqlitetest8_Init( interp ); Sqlite3.Sqlitetest9_Init( interp ); Sqlite3.Md5_Init( interp ); } } namespace tcl.lang { class ConsoleThread : SupportClass.ThreadClass { private class AnonymousClassTclEvent : TclEvent { public AnonymousClassTclEvent( string command, ConsoleThread enclosingInstance ) { InitBlock( command, enclosingInstance ); } private void InitBlock( string command, ConsoleThread enclosingInstance ) { this.command = command; this.enclosingInstance = enclosingInstance; } private string command; private ConsoleThread enclosingInstance; public ConsoleThread Enclosing_Instance { get { return enclosingInstance; } } public override int processEvent( int flags ) { // See if the command is a complete Tcl command if ( Interp.commandComplete( command ) ) { if ( tcl.lang.ConsoleThread.debug ) { WriteLine( "line was a complete command" ); } bool eval_exception = true; TclObject commandObj = TclString.newInstance( command ); try { commandObj.preserve(); Enclosing_Instance.interp.recordAndEval( commandObj, 0 ); eval_exception = false; } catch ( TclException e ) { if ( tcl.lang.ConsoleThread.debug ) { WriteLine( "eval returned exceptional condition" ); } TCL.CompletionCode code = e.getCompletionCode(); switch ( code ) { case TCL.CompletionCode.ERROR: Enclosing_Instance.putLine( Enclosing_Instance.err, Enclosing_Instance.interp.getResult().ToString() ); break; case TCL.CompletionCode.BREAK: Enclosing_Instance.putLine( Enclosing_Instance.err, "invoked \"break\" outside of a loop" ); break; case TCL.CompletionCode.CONTINUE: Enclosing_Instance.putLine( Enclosing_Instance.err, "invoked \"continue\" outside of a loop" ); break; default: Enclosing_Instance.putLine( Enclosing_Instance.err, "command returned bad code: " + code ); break; } } finally { commandObj.release(); } if ( !eval_exception ) { if ( tcl.lang.ConsoleThread.debug ) { WriteLine( "eval returned normally" ); } string evalResult = Enclosing_Instance.interp.getResult().ToString(); if ( tcl.lang.ConsoleThread.debug ) { WriteLine( "eval result was \"" + evalResult + "\"" ); } if ( evalResult.Length > 0 ) { Enclosing_Instance.putLine( Enclosing_Instance.out_Renamed, evalResult ); } } // Empty out the incoming command buffer Enclosing_Instance.sbuf.Length = 0; // See if the user set a custom shell prompt for the next command TclObject prompt; try { prompt = Enclosing_Instance.interp.getVar( "tcl_prompt1", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { prompt = null; } if ( prompt != null ) { try { Enclosing_Instance.interp.eval( prompt.ToString(), TCL.EVAL_GLOBAL ); } catch ( TclException e ) { Enclosing_Instance.put( Enclosing_Instance.out_Renamed, "% " ); } } else { Enclosing_Instance.put( Enclosing_Instance.out_Renamed, "% " ); } return 1; } else { // Interp.commandComplete() returned false if ( tcl.lang.ConsoleThread.debug ) { WriteLine( "line was not a complete command" ); } // We don't have a complete command yet. Print out a level 2 // prompt message and wait for further inputs. TclObject prompt; try { prompt = Enclosing_Instance.interp.getVar( "tcl_prompt2", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException ) { prompt = null; } if ( prompt != null ) { try { Enclosing_Instance.interp.eval( prompt.ToString(), TCL.EVAL_GLOBAL ); } catch ( TclException e ) { Enclosing_Instance.put( Enclosing_Instance.out_Renamed, "" ); } } else { Enclosing_Instance.put( Enclosing_Instance.out_Renamed, "" ); } return 1; } } // end processEvent method } // Interpreter associated with this console thread. internal Interp interp; // Collect the user input in this buffer until it forms a complete Tcl // command. internal StringBuilder sbuf; // Used to for interactive input/output private Channel out_Renamed; private Channel err; // set to true to get extra debug output public static bool debug = true; // used to keep track of wether or not System.in.available() works private static bool sysInAvailableWorks = false; internal ConsoleThread( Interp i ) { Name = "ConsoleThread"; interp = i; sbuf = new StringBuilder( 100 ); Debugger.Break();//TODO //out_Renamed = Tcl_IO.getStdChannel( StdChannel.STDOUT ); //err = TCL.TclIO.getStdChannel( StdChannel.STDERR ); } override public void Run() { if ( debug ) { WriteLine( "entered ConsoleThread run() method" ); } put( out_Renamed, "% " ); while ( true ) { // Loop forever to collect user inputs in a StringBuffer. // When we have a complete command, then execute it and print // out the results. // // The loop is broken under two conditions: (1) when EOF is // received inside getLine(). (2) when the "exit" command is // executed in the script. getLine(); string command = sbuf.ToString(); if ( debug ) { WriteLine( "got line from console" ); WriteLine( "\"" + command + "\"" ); } // When interacting with the interpreter, one must // be careful to never call a Tcl method from // outside of the event loop thread. If we did // something like just call interp.eval() it // could crash the whole process because two // threads might write over each other. // The only safe way to interact with Tcl is // to create an event and add it to the thread // safe event queue. AnonymousClassTclEvent Tevent = new AnonymousClassTclEvent( command, this ); // end TclEvent innerclass // Add the event to the thread safe event queue interp.getNotifier().queueEvent( Tevent, TCL.QUEUE_TAIL ); // Tell this thread to wait until the event has been processed. Tevent.sync(); } } private static void WriteLine( string s ) { System.Console.Out.WriteLine( s ); if ( debug ) System.Diagnostics.Debug.WriteLine( s ); } private void getLine() { sbuf.Append( Console.In.ReadLine() ); } private void putLine( Channel channel, string s ) // The String to print. { try { channel.write( interp, s ); channel.write( interp, "\n" ); channel.flush( interp ); } catch ( IOException ex ) { System.Console.Error.WriteLine( "IOException in Shell.putLine()" ); SupportClass.WriteStackTrace( ex, System.Console.Error ); } catch ( TclException ex ) { System.Console.Error.WriteLine( "TclException in Shell.putLine()" ); SupportClass.WriteStackTrace( ex, System.Console.Error ); } } private void put( Channel channel, string s ) // The String to print. { try { channel.write( interp, s ); channel.flush( interp ); } catch ( IOException ex ) { System.Console.Error.WriteLine( "IOException in Shell.put()" ); SupportClass.WriteStackTrace( ex, System.Console.Error ); } catch ( TclException ex ) { System.Console.Error.WriteLine( "TclException in Shell.put()" ); SupportClass.WriteStackTrace( ex, System.Console.Error ); } } static ConsoleThread() { { try { // There is no way to tell whether System.in will block AWT // threads, so we assume it does block if we can use // System.in.available(). long available = 0; // HACK ATK // available = System.Console.In.Length - System.Console.In.Position; int generatedAux5 = (int)available; sysInAvailableWorks = true; } catch ( System.Exception e ) { // If System.in.available() causes an exception -- it's probably // no supported on this platform (e.g. MS Java SDK). We assume // sysInAvailableWorks is false and let the user suffer ... } // Sun's JDK 1.2 on Windows systems is screwed up, it does not // echo chars to the console unless blocking IO is used. // For this reason we need to use blocking IO under Windows. if ( Util.Windows ) { sysInAvailableWorks = false; } if ( debug ) { WriteLine( "sysInAvailableWorks = " + sysInAvailableWorks ); } } } } // end of class ConsoleThread }