|
12 | 12 | use rustyscript::{Error, Module, Runtime, RuntimeOptions}; |
13 | 13 |
|
14 | 14 | fn main() -> Result<(), Error> { |
| 15 | + // First check if the feature is available |
| 16 | + if !check_feature_available()? { |
| 17 | + println!("The os_exit feature is not enabled."); |
| 18 | + println!("Try running: cargo run --example os_exit --features=\"os_exit\""); |
| 19 | + return Ok(()); |
| 20 | + } |
| 21 | + |
| 22 | + println!("Success! The os_exit feature is working correctly."); |
| 23 | + println!("JavaScript code now has access to Deno.exit() for script termination."); |
| 24 | + |
| 25 | + // Run each test with its own runtime for complete isolation |
| 26 | + test_basic_exit()?; |
| 27 | + test_runtime_survival()?; |
| 28 | + test_infinite_loop()?; |
| 29 | + |
| 30 | + Ok(()) |
| 31 | +} |
| 32 | + |
| 33 | +fn check_feature_available() -> Result<bool, Error> { |
15 | 34 | let module = Module::new( |
16 | 35 | "test_exit.js", |
17 | 36 | r#" |
18 | 37 | // Check if Deno.exit is available |
19 | 38 | if (typeof Deno !== 'undefined' && typeof Deno.exit === 'function') { |
20 | | - console.log("✓ Deno.exit is available"); |
21 | | - |
| 39 | + console.log("SUCCESS: Deno.exit is available"); |
| 40 | +
|
22 | 41 | // We can test the function exists but won't call it |
23 | 42 | // as that would terminate this example program |
24 | 43 | console.log(" Function signature:", Deno.exit.toString()); |
25 | 44 | } else { |
26 | | - console.log("✗ Deno.exit is not available"); |
| 45 | + console.log("FAILURE: Deno.exit is not available"); |
27 | 46 | console.log(" Make sure to compile with --features=\"os_exit\""); |
28 | 47 | } |
29 | | - |
| 48 | +
|
30 | 49 | export const hasExit = typeof Deno?.exit === 'function'; |
31 | 50 | "#, |
32 | 51 | ); |
33 | 52 |
|
34 | 53 | let mut runtime = Runtime::new(RuntimeOptions::default())?; |
35 | 54 | let module_handle = runtime.load_module(&module)?; |
| 55 | + runtime.get_value(Some(&module_handle), "hasExit") |
| 56 | +} |
36 | 57 |
|
37 | | - let has_exit: bool = runtime.get_value(Some(&module_handle), "hasExit")?; |
| 58 | +fn test_basic_exit() -> Result<(), Error> { |
| 59 | + println!("\nTesting immediate script exit..."); |
38 | 60 |
|
39 | | - if has_exit { |
40 | | - println!("Success! The os_exit feature is working correctly."); |
41 | | - println!("JavaScript code now has access to Deno.exit() for process termination."); |
42 | | - } else { |
43 | | - println!("The os_exit feature is not enabled."); |
44 | | - println!("Try running: cargo run --example os_exit --features=\"os_exit\""); |
| 61 | + // Create a fresh runtime for this test |
| 62 | + let mut runtime = Runtime::new(RuntimeOptions::default())?; |
| 63 | + |
| 64 | + let test_module = Module::new( |
| 65 | + "test_exit.js", |
| 66 | + r#" |
| 67 | + console.log("Before Deno.exit(42)"); |
| 68 | +
|
| 69 | + // This should throw an immediate exception - no further code should execute |
| 70 | + Deno.exit(42); |
| 71 | +
|
| 72 | + // CRITICAL TEST: These lines should NEVER execute |
| 73 | + console.log("FAILURE: This line executed after Deno.exit()!"); |
| 74 | + globalThis.POST_EXIT_EXECUTED = true; |
| 75 | + throw new Error("Post-exit code executed - immediate termination failed!"); |
| 76 | + "#, |
| 77 | + ); |
| 78 | + |
| 79 | + let result = runtime.load_module(&test_module); |
| 80 | + |
| 81 | + let Err(e) = result else { |
| 82 | + return Err(Error::Runtime( |
| 83 | + "CRITICAL: Script completed without immediate exit!".to_string(), |
| 84 | + )); |
| 85 | + }; |
| 86 | + |
| 87 | + let Some(code) = e.as_script_exit() else { |
| 88 | + return Err(Error::Runtime(format!("ERROR: Unexpected error: {}", e))); |
| 89 | + }; |
| 90 | + |
| 91 | + println!( |
| 92 | + "SUCCESS: Basic test - Script exited immediately with code: {}", |
| 93 | + code |
| 94 | + ); |
| 95 | + |
| 96 | + // Verify no post-exit globals were set |
| 97 | + match runtime.eval::<bool>("typeof globalThis.POST_EXIT_EXECUTED !== 'undefined'") { |
| 98 | + Ok(false) => { |
| 99 | + println!("SUCCESS: Immediate termination verified - No post-exit code executed") |
| 100 | + } |
| 101 | + Ok(true) => { |
| 102 | + return Err(Error::Runtime( |
| 103 | + "CRITICAL: Post-exit code executed!".to_string(), |
| 104 | + )) |
| 105 | + } |
| 106 | + Err(_) => { |
| 107 | + println!("SUCCESS: Immediate termination verified - No post-exit globals accessible") |
| 108 | + } |
45 | 109 | } |
46 | 110 |
|
47 | 111 | Ok(()) |
48 | 112 | } |
| 113 | + |
| 114 | +fn test_runtime_survival() -> Result<(), Error> { |
| 115 | + // Create a fresh runtime for this test |
| 116 | + let mut runtime = Runtime::new(RuntimeOptions::default())?; |
| 117 | + |
| 118 | + // First exit the runtime |
| 119 | + let exit_module = Module::new( |
| 120 | + "exit_test.js", |
| 121 | + r#" |
| 122 | + console.log("About to exit..."); |
| 123 | + Deno.exit(0); |
| 124 | + "#, |
| 125 | + ); |
| 126 | + |
| 127 | + let _ = runtime.load_module(&exit_module); // Ignore the exit error |
| 128 | + |
| 129 | + // Now verify the runtime still works |
| 130 | + let result: String = runtime.eval("'Runtime still works after immediate termination!'")?; |
| 131 | + println!("SUCCESS: Runtime survival test - {}", result); |
| 132 | + Ok(()) |
| 133 | +} |
| 134 | + |
| 135 | +fn test_infinite_loop() -> Result<(), Error> { |
| 136 | + println!("\nTesting script exit from infinite loop..."); |
| 137 | + |
| 138 | + // Create a fresh runtime for this test |
| 139 | + let mut runtime = Runtime::new(RuntimeOptions::default())?; |
| 140 | + |
| 141 | + let infinite_loop_module = Module::new( |
| 142 | + "infinite_loop_test.js", |
| 143 | + r#" |
| 144 | + console.log("Starting infinite loop test"); |
| 145 | + let count = 0; |
| 146 | + while (true) { |
| 147 | + count++; |
| 148 | + if (count > 1000000) { |
| 149 | + console.log("Calling Deno.exit from within infinite loop"); |
| 150 | + Deno.exit(99); |
| 151 | +
|
| 152 | + // CRITICAL TEST: These lines should NEVER execute due to immediate termination |
| 153 | + console.log("FAILURE: Code executed after Deno.exit() in infinite loop!"); |
| 154 | + globalThis.INFINITE_LOOP_POST_EXIT_EXECUTED = true; |
| 155 | + break; // This should never be reached |
| 156 | + } |
| 157 | + } |
| 158 | + console.log("FAILURE: End of infinite loop reached!"); |
| 159 | + globalThis.INFINITE_LOOP_COMPLETED = true; |
| 160 | + "#, |
| 161 | + ); |
| 162 | + |
| 163 | + let result = runtime.load_module(&infinite_loop_module); |
| 164 | + |
| 165 | + let Err(e) = result else { |
| 166 | + return Err(Error::Runtime( |
| 167 | + "ERROR: Unexpected - infinite loop script completed without exiting".to_string(), |
| 168 | + )); |
| 169 | + }; |
| 170 | + |
| 171 | + let Some(code) = e.as_script_exit() else { |
| 172 | + return Err(Error::Runtime(format!( |
| 173 | + "ERROR: Unexpected error from infinite loop: {}", |
| 174 | + e |
| 175 | + ))); |
| 176 | + }; |
| 177 | + |
| 178 | + println!( |
| 179 | + "SUCCESS: Infinite loop script exited cleanly with code: {}", |
| 180 | + code |
| 181 | + ); |
| 182 | + |
| 183 | + // CRITICAL: Verify no post-exit code executed in infinite loop |
| 184 | + match runtime.eval::<bool>("typeof globalThis.INFINITE_LOOP_POST_EXIT_EXECUTED !== 'undefined'") |
| 185 | + { |
| 186 | + Ok(false) => { |
| 187 | + println!("SUCCESS: Infinite loop immediate termination verified - No post-exit code executed") |
| 188 | + } |
| 189 | + Ok(true) => { |
| 190 | + return Err(Error::Runtime( |
| 191 | + "CRITICAL: Post-exit code executed in infinite loop!".to_string(), |
| 192 | + )) |
| 193 | + } |
| 194 | + Err(_) => println!( |
| 195 | + "SUCCESS: Infinite loop immediate termination verified - No post-exit globals accessible" |
| 196 | + ), |
| 197 | + } |
| 198 | + |
| 199 | + // Also verify the loop didn't complete normally |
| 200 | + match runtime.eval::<bool>("typeof globalThis.INFINITE_LOOP_COMPLETED !== 'undefined'") { |
| 201 | + Ok(false) => { |
| 202 | + println!("SUCCESS: Infinite loop properly terminated - Loop did not complete normally") |
| 203 | + } |
| 204 | + Ok(true) => { |
| 205 | + return Err(Error::Runtime( |
| 206 | + "CRITICAL: Infinite loop completed normally after exit!".to_string(), |
| 207 | + )) |
| 208 | + } |
| 209 | + Err(_) => { |
| 210 | + println!("SUCCESS: Infinite loop properly terminated - No completion flags accessible") |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + println!("SUCCESS: Runtime survived the infinite loop!"); |
| 215 | + |
| 216 | + // Test that the runtime can still execute code after infinite loop |
| 217 | + let result: String = runtime.eval("'Runtime still works after infinite loop!'")?; |
| 218 | + println!("SUCCESS: Post-infinite-loop test - {}", result); |
| 219 | + |
| 220 | + Ok(()) |
| 221 | +} |
0 commit comments