(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*            David Allsopp, University of Cambridge & Tarides            *)
(*                                                                        *)
(*   Copyright 2024 David Allsopp Ltd.                                    *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

open Harness.Import

(* This test verifies that a series of libraries can be loaded in a toplevel.
   Any failures cause the script to be aborted. *)
let run config env mode =
  let toplevel = Environment.tool_path env mode "ocaml" "ocamlnat" in
  Format.printf "Testing loading of libraries in %a\n%!"
                (Environment.pp_path env) toplevel;
  let test_libraries_in_toplevel libraries =
    let has_c_stubs =
      (* Generate a test script for loading each library in a separate toplevel
         instance. *)
      Out_channel.with_open_text "test_install_script.ml" (fun oc ->
        let has_c_stubs =
          List.fold_left (fun c_bindings library ->
            let ext =
              match mode with
              | Native ->
                  if library = "dynlink" then
                    (* dynlink.cmxs does not exist, for obvious reasons, but we
                       can check loading the library in ocamlnat "works". *)
                    "cmxa"
                  else if library = "threads" then
                    let threads_plugin =
                      let plugin = Filename.concat "threads" "threads.cmxs" in
                      Environment.in_libdir env plugin
                    in
                    if Sys.file_exists threads_plugin then
                      Harness.fail_because
                        "threads.cmxs is not expected to exist"
                    else
                      (* cf. ocaml/ocaml#12250 - no threads.cmxs *)
                      "cmxa"
                  else
                    "cmxs"
              | Bytecode ->
                  "cma"
            in
            Printf.fprintf oc
              "#directory \"+%s\";;\n\
               #load \"%s.%s\";;\n\
               print_endline \"Loaded %s.%s\";;"
            library library ext library ext;
            (c_bindings
             || (library <> "dynlink" && mode = Bytecode))) false libraries
        in
        Printf.fprintf oc "#quit;;\n";
        has_c_stubs)
    in
    let args =
      ["-noinit"; "-no-version"; "-noprompt"; "test_install_script.ml"]
    in
    let expected_exit_code =
      (* Systems configured with --disable-shared can't load bytecode libraries
         which need C stubs *)
      if Sys.cygwin && mode = Native && List.mem "unix" libraries
      || has_c_stubs && not Config.supports_shared_libraries then
        (* cf. ocaml/flexdll#146 - Cygwin's ocamlnat can't load unix.cmxs *)
        125
      else
        0
    in
    let exit_code, output =
      (* In the Renamed phase, the ocaml binary will only be able to start if
         the launcher searches for ocamlrun (as the Windows executable launcher
         does). CAML_LD_LIBRARY_PATH will need to be set if any of the libraries
         being loaded need C stubs. Finally, Config.standard_library will still
         point to the Original location, requiring OCAMLLIB to be set for the
         toplevel to start at all. *)
      Environment.run_process
        ~fails:(expected_exit_code <> 0)
        ~runtime:(mode = Bytecode && not config.launcher_searches_for_ocamlrun)
        ~stdlib:(config.has_relative_libdir = None) env toplevel args
    in
    Environment.display_output output;
    if exit_code <> expected_exit_code then
      Harness.fail_because "%s was expected to exit with code %d"
                           toplevel expected_exit_code;
    Sys.remove "test_install_script.ml"
  in
  List.iter test_libraries_in_toplevel config.libraries
