# file      : tests/cc/modules/modules.testscript
# license   : MIT; see accompanying LICENSE file

# Test modules proper.
#

.include common.testscript

+$modules || exit

# Common source files that are symlinked in the test directories if used.
#
+cat <<EOI >=core.mxx
#ifndef LIBFOO_EXPORT
#  define LIBFOO_EXPORT
#endif

export module foo.core;
export LIBFOO_EXPORT int f (int);
EOI

+cat <<EOI >=core.cxx
module foo.core;
int f (int i) {return i - 1;}
EOI

+cat <<EOI >=driver.cxx
import foo.core;
int main (int argc, char*[]) {return f (argc);}
EOI

: bmi-combined
:
: Test combined interface/implementation unit specified as bmi{}.
:
cp ../core.mxx ./ && cat >+core.mxx <<EOI;
  int f (int i) {return i - 1;}
  EOI
ln -s ../driver.cxx ./;
$* test clean <<EOI
  exe{test}: cxx{driver} bmi{core}
  bmi{core}: mxx{core}
  EOI

: mxx-combined
:
: Test combined interface/implementation unit specified as mxx{}.
:
cp ../core.mxx ./ && cat >+core.mxx <<EOI;
  int f (int i) {return i - 1;}
  EOI
ln -s ../driver.cxx ./;
$* test clean <<EOI
  exe{test}: cxx{driver} mxx{core}
  EOI

: bmi-separate
:
: Test separate interface/implementation unit specified as bmi{}.
:
ln -s ../core.mxx ../core.cxx ../driver.cxx ./;
$* test clean <<EOI
  exe{test}: cxx{driver} {bmi cxx}{core}
  bmi{core}: mxx{core}
  EOI

: mxx-separate
:
: Test separate interface/implementation unit specified as mxx{}.
:
ln -s ../core.mxx ../core.cxx ../driver.cxx ./;
$* test clean <<EOI
  exe{test}: cxx{driver} {mxx cxx}{core}
  EOI

: name-match
:
: Test fuzzy/explicit match between module name and file name.
:
{
  # "Bad" match which we should better.
  #
  +cat <<EOI >=core.mxx
    export module bar.core;
    EOI

  +cat <<EOI >=ext-core.mxx
    export module foo.ext_core;
    EOI

  : separator
  :
  : Test separator equivalence.
  :
  ln -s ../../core.mxx foo-core.mxx;
  ln -s ../core.mxx ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{core foo-core}'

  : case
  :
  : Test case-insensitivity and case-change as a separator.
  :
  ln -s ../../core.mxx FooCore.mxx;
  ln -s ../core.mxx ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{core FooCore}'

  : dir
  :
  : Test subdirectory.
  :
  mkdir foo;
  ln -s ../../../core.mxx foo/core.mxx;
  ln -s ../core.mxx ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{core} foo/mxx{core}'

  : explicit
  :
  : Explicit module name.
  :
  ln -s ../../core.mxx baz.mxx;
  ln -s ../core.mxx ../../core.cxx ../../driver.cxx ./;
  $* test clean <<EOO
    exe{test}: cxx{driver core} mxx{core baz}
    mxx{baz}@./: cxx.module_name = foo.core
    EOO

  : secondary-entire
  :
  : Secondary score: core.mxx vs ext-core.mxx (entire file name consumed).
  :
  ln -s ../ext-core.mxx ../../core.mxx ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{ext-core core}'

  : secondary-dir-separator
  :
  : Secondary score: sub/core.mxx vs ext-core.mxx (stronger separator).
  :
  mkdir sub;
  ln -s ../../../core.mxx ../../../core.cxx sub/;
  ln -s ../ext-core.mxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver} mxx{ext-core} sub/{mxx cxx}{core}'

  : secondary-real-separator
  :
  : Secondary score: sub-core.mxx vs extcore.mxx (real separator).
  :
  ln -s ../ext-core.mxx extcore.mxx;
  ln -s ../../core.mxx sub-core.mxx;
  ln -s ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{extcore sub-core}'

  : secondary-case-separator
  :
  : Secondary score: SubCore.mxx vs extcore.mxx (case separator).
  :
  ln -s ../ext-core.mxx extcore.mxx;
  ln -s ../../core.mxx SubCore.mxx;
  ln -s ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{extcore SubCore}'

  : secondary-unmatched
  :
  : Secondary score: gtk-core.mxx vs gtk-ext-core.mxx (unmatched-length).
  :
  ln -s ../ext-core.mxx gtk-ext-core.mxx;
  ln -s ../../core.mxx gtk-core.mxx;
  ln -s ../../core.cxx ../../driver.cxx ./;
  $* test clean <'exe{test}: cxx{driver core} mxx{gtk-ext-core gtk-core}'
}

: unresolved
:
ln -s ../driver.cxx ./;
$* test &*.d <'exe{test}: cxx{driver}' 2>>EOE != 0
  driver.cxx: error: unable to resolve module foo.core
    info: verify module interface is listed as a prerequisite, otherwise
    info: consider adjusting module interface file names or
    info: consider specifying module name with cxx.module_name
  EOE

: misguessed
:
ln -s ../core.mxx ./;
cat <'import bar.core;' >=driver.cxx;
$* test &*.d &?*.ii* <'exe{test}: cxx{driver} mxx{core}' 2>>EOE != 0
  driver.cxx: error: failed to correctly guess module name from mxx{core}
    info: guessed: bar.core
    info: actual:  foo.core
    info: consider adjusting module interface file names or
    info: consider specifying module name with cxx.module_name
  EOE

: library
:
: Test importing a module from a library.
:
ln -s ../core.mxx ../core.cxx ../driver.cxx ./;
$* test clean <<EOI
  ./: lib{foo} exe{test} # Full build.
  exe{test}: cxx{driver} lib{foo}
  lib{foo}: {mxx cxx}{core}
  EOI

: module-marker
:
: Test global module fragment/leading module marker (module;).
:
if ($cxx.id != 'msvc') # Disabled for MSVC due to issue 845845.
{
cat <<EOI >=g.hxx;
void g ();
EOI
cat <<EOI >=core.mxx;
#if __cpp_modules >= 201810
module;
#endif

#include "g.hxx"
EOI
cat <<<../core.mxx >+core.mxx;
ln -s ../core.cxx ../driver.cxx ./;
$* test clean <<EOI
  exe{test}: cxx{driver} {mxx cxx}{core}
  EOI
}

: re-export
:
: Test module re-exporting (export import M;)
:
{
  +cat <<EOI >=base.mxx
    export module foo.base;
    export import foo.core;
    EOI

  +cat <<EOI >=extra.mxx
    #ifndef LIBFOO_EXPORT
    #  define LIBFOO_EXPORT
    #endif

    export module foo.extra;

    export import foo.base; // Note: cannot be combined with the below.

    export
    {
      // VC appears to require dll-export of inline functions.
      //
      LIBFOO_EXPORT inline int g (int i) {return i != 0 ? i : -1;}
    }
    EOI

  +cat <<EOI >=foo.mxx
    export module foo;

    export import foo.core;
    export import foo.base;
    export import foo.extra;
    EOI

  : basic
  :
  ln -s ../base.mxx ../../core.mxx ../../core.cxx ./;
  cat <<EOI >=driver.cxx;
    import foo.base;
    int main (int argc, char*[]) {return f (argc);}
    EOI
  $* test clean <'exe{test}: cxx{driver core} mxx{core base}'

  : recursive
  :
  ln -s ../base.mxx ../extra.mxx ../../core.mxx ../../core.cxx ./;
  cat <<EOI >=driver.cxx;
    import foo.extra;
    int main (int argc, char*[]) {return f (g (argc));}
    EOI
  $* test clean <'exe{test}: cxx{driver core} mxx{core base extra}'

  : duplicate
  :
  ln -s ../base.mxx ../extra.mxx ../foo.mxx ../../core.mxx ../../core.cxx ./;
  cat <<EOI >=driver.cxx;
    import foo;
    int main (int argc, char*[]) {return f (g (argc));}
    EOI
  $* test clean <'exe{test}: cxx{driver core} mxx{core base extra foo}'

  : library
  :
  ln -s ../base.mxx ../extra.mxx ../foo.mxx ../../core.mxx ../../core.cxx ./;
  cat <<EOI >=driver.cxx;
    import foo;
    int main (int argc, char*[]) {return f (g (argc));}
    EOI
  $* test clean <<EOI
    exe{test}: cxx{driver} mxx{foo} lib{foo}
    lib{foo}: mxx{core base extra} cxx{core}
    EOI
}

: import
:
: Test module import. Currently, all the implementation require access to the
: entire, recursively-explored list of BMIs.
:
{
  +cat <<EOI >=base.mxx
    export module foo.base;
    import foo.core;
    export int g (int i) {return f (i);}
    EOI

  +cat <<EOI >=extra.mxx
    export module foo.extra;
    import foo.base;
    export int h (int i) {return g (i);}
    EOI

  : basic
  :
  ln -s ../base.mxx ../../core.mxx ../../core.cxx ./;
  cat <<EOI >=driver.cxx;
    import foo.base;
    int main (int argc, char*[]) {return g (argc);}
    EOI
  $* test clean <'exe{test}: cxx{driver core} mxx{core base}'

  : recursive
  :
  ln -s ../base.mxx ../extra.mxx ../../core.mxx ../../core.cxx ./;
  cat <<EOI >=driver.cxx;
    import foo.extra;
    int main (int argc, char*[]) {return h (argc);}
    EOI
  $* test clean <'exe{test}: cxx{driver core} mxx{core base extra}'
}

: resolve-change
:
: Test detection of module name to BMI resolution change.
:
ln -s ../core.mxx ../core.cxx ../driver.cxx ./;
cat <<EOI >=foo-core.mxx;
  export module foo.core;
  export inline int f (int i) {return i - 2;}
  EOI
$* update <<EOI;
  ./: exe{test} bmie{foo-core}
  exe{test}: cxx{driver} {mxx cxx}{core}
  bmie{foo-core}: mxx{foo-core}
  EOI
$* test --verbose 1 <<EOI 2>>EOE;
  exe{test}: cxx{driver} {mxx}{foo-core}
  exe{test}: test.arguments = two
  EOI
  c++ cxx{driver} -> obje{driver}
  ld exe{test}
  test exe{test}
  EOE
$* test clean <<EOI
  ./: exe{test} bmie{foo-core}
  exe{test}: cxx{driver} {mxx cxx}{core}
  bmie{foo-core}: mxx{foo-core}
  EOI

: symexport
:
: Test the __symexport feature.
:
cat <<EOI >=core.mxx;
  export module foo.core;

  export __symexport int f (int);

  __symexport int g_impl (int i) {return i - 1;}
  export __symexport inline int g (int i) {return g_impl (i);}
  EOI
ln -s ../core.cxx core-f.cxx;
cat <<EOI >=core-g.cxx;
  module foo.core;
  int g_impl (int i) {return i - 1;}
  EOI
cat <<EOI >=driver.cxx;
  import foo.core;
  int main (int argc, char*[]) {return f (argc) + g (argc);}
  EOI
$* test clean <<EOI
  ./: lib{foo} exe{test} # Full build.
  exe{test}: cxx{driver} lib{foo}
  lib{foo}: mxx{core} cxx{core-f} # @@ VC: core-g
  EOI