@@ -3279,6 +3279,137 @@ let stdlib_04a_mut_tests = [
32793279 Alcotest. test_case " #328 Deno codegen emits __cell shape" `Quick test_stdlib_04a_mut_deno_codegen;
32803280]
32813281
3282+ (* ---- STDLIB-04d: IO externs hermetic test coverage (Refs #331) ----
3283+
3284+ `print`/`println`/`read_line`/`read_file`/`write_file` were already
3285+ wired in interp + Deno codegen, but had no dedicated hermetic tests
3286+ asserting the round-trip semantics (test-debt, not impl-debt). This
3287+ row adds them. `read_line` is interactive and skipped here — that
3288+ surface is exercised by the TEA-bridge tests with redirected stdin. *)
3289+
3290+ (* write_file -> read_file round-trip on a real tmpfile *)
3291+ let test_stdlib_04d_write_then_read_file () =
3292+ let tmp = Filename. temp_file " as_04d_io" " .txt" in
3293+ Fun. protect ~finally: (fun () -> if Sys. file_exists tmp then Sys. remove tmp)
3294+ (fun () ->
3295+ let src = Printf. sprintf {|
3296+ fn writer() -> Result < Unit , String > { write_file(" %s" , " hello-04d\n " ) }
3297+ fn reader() -> Result < String , String > { read_file(" %s" ) }
3298+ | } (String. escaped tmp) (String. escaped tmp) in
3299+ let prog = Parse_driver. parse_string ~file: " <test>" src in
3300+ match Interp. eval_program prog with
3301+ | Error e -> Alcotest. failf " interp load failed: %s"
3302+ (Value. show_eval_error e)
3303+ | Ok env ->
3304+ let call name =
3305+ match Value. lookup_env name env with
3306+ | Error e -> Error e
3307+ | Ok fn -> Interp. apply_function fn []
3308+ in
3309+ (match call " writer" with
3310+ | Ok (Value. VVariant ("Ok" , _ )) -> ()
3311+ | Ok v -> Alcotest. failf " writer expected Ok(Unit), got %s"
3312+ (Value. show_value v)
3313+ | Error e -> Alcotest. failf " writer failed: %s"
3314+ (Value. show_eval_error e));
3315+ (match call " reader" with
3316+ | Ok (Value. VVariant ("Ok" , Some (Value. VString s ))) ->
3317+ Alcotest. (check string ) " reader returns written content"
3318+ " hello-04d\n " s
3319+ | Ok v -> Alcotest. failf " reader expected Ok(String), got %s"
3320+ (Value. show_value v)
3321+ | Error e -> Alcotest. failf " reader failed: %s"
3322+ (Value. show_eval_error e)))
3323+
3324+ (* read_file on a path that does not exist returns Err, not raises. *)
3325+ let test_stdlib_04d_read_file_missing () =
3326+ let tmp = Filename. temp_file " as_04d_missing" " .txt" in
3327+ Sys. remove tmp; (* removed -- guaranteed missing *)
3328+ let src = Printf. sprintf
3329+ " fn f() -> Result<String, String> { read_file(\" %s\" ) }"
3330+ (String. escaped tmp) in
3331+ let prog = Parse_driver. parse_string ~file: " <test>" src in
3332+ match Interp. eval_program prog with
3333+ | Error e -> Alcotest. failf " interp failed: %s" (Value. show_eval_error e)
3334+ | Ok env ->
3335+ (match Value. lookup_env " f" env with
3336+ | Ok fn ->
3337+ (match Interp. apply_function fn [] with
3338+ | Ok (Value. VVariant ("Err" , _ )) -> ()
3339+ | Ok v -> Alcotest. failf " expected Err(_), got %s" (Value. show_value v)
3340+ | Error e -> Alcotest. failf " apply failed: %s"
3341+ (Value. show_eval_error e))
3342+ | Error e -> Alcotest. failf " lookup failed: %s"
3343+ (Value. show_eval_error e))
3344+
3345+ (* `print` and `println` exec without error. Stdout-content capture is
3346+ intentionally out of scope here (the TEA-bridge tests already
3347+ exercise the redirect path with full Unix.dup2 plumbing); we just
3348+ prove the lowering doesn't blow up at runtime. *)
3349+ let test_stdlib_04d_print_no_error () =
3350+ let src = " fn f() -> Unit { print(\"\" ) }" in
3351+ let prog = Parse_driver. parse_string ~file: " <test>" src in
3352+ match Interp. eval_program prog with
3353+ | Error e -> Alcotest. failf " interp failed: %s" (Value. show_eval_error e)
3354+ | Ok env ->
3355+ (match Value. lookup_env " f" env with
3356+ | Ok fn ->
3357+ (match Interp. apply_function fn [] with
3358+ | Ok _ -> ()
3359+ | Error e -> Alcotest. failf " print failed: %s"
3360+ (Value. show_eval_error e))
3361+ | Error e -> Alcotest. failf " lookup failed: %s"
3362+ (Value. show_eval_error e))
3363+
3364+ let test_stdlib_04d_println_no_error () =
3365+ let src = " fn f() -> Unit { println(\"\" ) }" in
3366+ let prog = Parse_driver. parse_string ~file: " <test>" src in
3367+ match Interp. eval_program prog with
3368+ | Error e -> Alcotest. failf " interp failed: %s" (Value. show_eval_error e)
3369+ | Ok env ->
3370+ (match Value. lookup_env " f" env with
3371+ | Ok fn ->
3372+ (match Interp. apply_function fn [] with
3373+ | Ok _ -> ()
3374+ | Error e -> Alcotest. failf " println failed: %s"
3375+ (Value. show_eval_error e))
3376+ | Error e -> Alcotest. failf " lookup failed: %s"
3377+ (Value. show_eval_error e))
3378+
3379+ (* Deno codegen lowers IO externs to the right host shape. *)
3380+ let test_stdlib_04d_io_deno_codegen () =
3381+ let src = {|
3382+ fn run() -> Unit {
3383+ print(" a" );
3384+ println(" b" )
3385+ }
3386+ | } in
3387+ let prog = Parse_driver. parse_string ~file: " <test>" src in
3388+ let loader = Module_loader. create (Module_loader. default_config () ) in
3389+ match Resolve. resolve_program_with_loader prog loader with
3390+ | Error (e , _ ) ->
3391+ Alcotest. failf " resolve failed: %s" (Resolve. show_resolve_error e)
3392+ | Ok (rctx , _ ) ->
3393+ (match Codegen_deno. codegen_deno prog rctx.symbols with
3394+ | Error e -> Alcotest. failf " deno-codegen failed: %s" e
3395+ | Ok js ->
3396+ let contains needle =
3397+ let nl = String. length needle and sl = String. length js in
3398+ let rec go i = i + nl < = sl &&
3399+ (String. sub js i nl = needle || go (i + 1 ))
3400+ in nl = 0 || go 0
3401+ in
3402+ Alcotest. (check bool ) " prelude defines print/println"
3403+ true (contains " const print" && contains " const println" ))
3404+
3405+ let stdlib_04d_io_tests = [
3406+ Alcotest. test_case " #331 write_file -> read_file round-trip" `Quick test_stdlib_04d_write_then_read_file;
3407+ Alcotest. test_case " #331 read_file on missing path returns Err" `Quick test_stdlib_04d_read_file_missing;
3408+ Alcotest. test_case " #331 print exec without error" `Quick test_stdlib_04d_print_no_error;
3409+ Alcotest. test_case " #331 println exec without error" `Quick test_stdlib_04d_println_no_error;
3410+ Alcotest. test_case " #331 Deno codegen wires print/println" `Quick test_stdlib_04d_io_deno_codegen;
3411+ ]
3412+
32823413(* ---- STDLIB-04e: Pure externs (Refs #332) ----
32833414
32843415 Three externs declared in stdlib/effects.affine as pure:
@@ -3354,6 +3485,8 @@ let stdlib_04e_pure_tests = [
33543485 Alcotest. test_case " #332 string_to_int(\" 123\" ) == Some(123)" `Quick test_stdlib_04e_string_to_int_some;
33553486 Alcotest. test_case " #332 string_to_int(\" abc\" ) == None" `Quick test_stdlib_04e_string_to_int_none;
33563487 Alcotest. test_case " #332 string_length(\" hello\" ) == 5" `Quick test_stdlib_04e_string_length;
3488+ ]
3489+
33573490(* ---- STDLIB-04b: Throws extern `error<T>` (Refs #329) ----
33583491
33593492 `error<T>(msg: String) -> T / Throws` was declared in
@@ -3438,6 +3571,7 @@ let stdlib_04b_error_tests = [
34383571 Alcotest. test_case " #329 Deno codegen lowers to throw" `Quick test_stdlib_04b_error_deno_codegen;
34393572]
34403573
3574+
34413575(* ---- Issue #35 Phase 2 — Vscode bindings ----
34423576
34433577 Verifies stdlib/Vscode.affine and stdlib/VscodeLanguageClient.affine
@@ -4129,6 +4263,7 @@ let tests =
41294263 (" E2E Xmod Other Codegens" , cross_module_other_codegens_tests);
41304264 (" E2E Externs" , extern_tests);
41314265 (" E2E STDLIB-04a Mut #328" , stdlib_04a_mut_tests);
4266+ (" E2E STDLIB-04d IO #331" , stdlib_04d_io_tests);
41324267 (" E2E STDLIB-04e Pure #332" , stdlib_04e_pure_tests);
41334268 (" E2E STDLIB-04b error #329" , stdlib_04b_error_tests);
41344269 (" E2E Vscode Bindings" , vscode_bindings_tests);
0 commit comments