diff --git a/doc/developer/advance.tex b/doc/developer/advance.tex
index d6dceaa1bf73026c3a1f3f6ba952b3c59e8b5504..76c7992b5973dc2c528fe850425104d52621f427 100644
--- a/doc/developer/advance.tex
+++ b/doc/developer/advance.tex
@@ -745,7 +745,7 @@ occurs currently in the following cases:
 
 \begin{example}
   Test \texttt{tests/sparecode/calls.c} declares the following directives.
-  \sscodeidx{Test}{Directive}{OPT}
+  \nscodeidx{Test!Directive}{OPT}
 \begin{listing-nonumber}
 /* run.config
    OPT: -sparecode-analysis
@@ -895,54 +895,59 @@ the configuration header of a test (or a test suite).
 \textbf{Kind} & \textbf{Name} & \textbf{Specification} & \textbf{default}\\
 \hline
 \hline \multirow{4}{23mm}{\centering{Command}}
-& \texttt{CMD}\sscodeidxdef{Test}{Directive}{CMD}
+& \texttt{CMD}\nscodeidxdef{Test!Directive}{CMD}
 & Program to run
 & \texttt{./bin/toplevel.opt}
 \\
-& \texttt{OPT}\sscodeidxdef{Test}{Directive}{OPT}
+& \texttt{OPT}\nscodeidxdef{Test!Directive}{OPT}
 & Options given to the program
 & \texttt{-val -out -input -deps}
 \\
-& \texttt{STDOPT}\sscodeidxdef{Test}{Directive}{STDOPT}
+& \texttt{STDOPT}\nscodeidxdef{Test!Directive}{STDOPT}
 & Add and remove options from the default set
 & \textit{None}
 \\
-& \texttt{LOG}\sscodeidxdef{Test}{Directive}{LOG}
+& \texttt{LOG}\nscodeidxdef{Test!Directive}{LOG}
 & Add an additional file to compare against an oracle
 & \textit{None}
 \\ 
-& \texttt{EXECNOW}\sscodeidxdef{Test}{Directive}{EXECNOW}
+& \texttt{EXECNOW}\nscodeidxdef{Test!Directive}{EXECNOW}
 & Run a command before the following commands. When specified in a configuration
 file, run it only once.
 & \textit{None}
 \\
-& \texttt{EXEC}\sscodeidxdef{Test}{Directive}{EXEC}
+& \texttt{EXEC}\nscodeidxdef{Test!Directive}{EXEC}
 & Similar to \texttt{EXECNOW}, but run it once per testing file.
 & \textit{None}
 \\
-& \texttt{MACRO}\sscodeidxdef{Test}{Directive}{MACRO}
+& \texttt{MACRO}\nscodeidxdef{Test!Directive}{MACRO}
 & Define a new macro
 & \textit{None}
 \\
-& \texttt{FILTER}\sscodeidxdef{Test}{Directive}{FILTER}
+& \texttt{FILTER}\nscodeidxdef{Test!Directive}{FILTER}
 & Command used to filter results
 & \textit{None}
 \\
+& \texttt{MODULE}\nscodeidxdef{Test!Directive}{MODULE}
+& Register a dynamic module to be built and to be loaded with each subsequent
+test
+& \textit{None}
+\\
 \hline \multirow{2}{23mm}{\centering{Test suite}}
-& \texttt{DONTRUN}\sscodeidxdef{Test}{Directive}{DONTRUN}
+& \texttt{DONTRUN}\nscodeidxdef{Test!Directive}{DONTRUN}
 & Do not execute this test
 & \textit{None}
 \\
-& \texttt{FILEREG}\sscodeidxdef{Test}{Directive}{FILEREG}
+& \texttt{FILEREG}\nscodeidxdef{Test!Directive}{FILEREG}
 & selects the files to test
 & \texttt{.*\bss.\bss(c|i\bss)}
 \\
 \hline \multirow{2}{23mm}{\centering{Miscellaneous}}
-& \texttt{COMMENT}\sscodeidxdef{Test}{Directive}{COMMENT}
+& \texttt{COMMENT}\nscodeidxdef{Test!Directive}{COMMENT}
 & Comment in the configuration
 & \textit{None}
 \\
-& \texttt{GCC}\sscodeidxdef{Test}{Directive}{GCC}
+& \texttt{GCC}\nscodeidxdef{Test!Directive}{GCC}
 & Unused (compatibility only)
 & \textit{None}
 \\
@@ -988,12 +993,12 @@ the sequence above is read in order and defines a configuration level
 
 \begin{itemize}
 \item
-  \texttt{CMD} allows to change the command that is used for the
+  \texttt{CMD} allows changing the command that is used for the
   following \texttt{OPT} directives (until a new \texttt{CMD}
   directive is found). No new test case is generated
   if there is no further \texttt{OPT} directive. At a given
   configuration level, the default value for directive
-  \texttt{CMD}\sscodeidxdef{Test}{Directive}{CMD} is the last
+  \texttt{CMD}\nscodeidx{Test!Directive}{CMD} is the last
   \texttt{CMD} directive of the preceding configuration level.
 \item \texttt{LOG} adds a file to be compared against an oracle in the
   next \texttt{OPT} directive. Several files can be monitored from a single
@@ -1007,7 +1012,7 @@ the sequence above is read in order and defines a configuration level
   configuration level, they correspond to different test cases. The
   \texttt{OPT} directive(s) of a given configuration level replace(s)
   the ones of the preceding level.
-\item The \texttt{STDOPT}\sscodeidxdef{Test}{Directive}{STDOPT}
+\item The \texttt{STDOPT}\nscodeidx{Test!Directive}{STDOPT}
   directive takes as default set of options
   the last \texttt{OPT} directive(s) of the preceding configuration
   level. If the preceding configuration level contains several
@@ -1025,8 +1030,8 @@ options). As with \texttt{OPT}, each \texttt{STDOPT} corresponds to a different
 (set of) test case(s). \texttt{LOG} directives preceding an \texttt{STDOPT}
 are taken into account.
 \item The syntax for directives
-  \texttt{EXECNOW}\sscodeidxdef{Test}{Directive}{EXECNOW}
-  and \texttt{EXEC}\sscodeidxdef{Test}{Directive}{EXEC} is the following.
+  \texttt{EXECNOW}\nscodeidx{Test!Directive}{EXECNOW}
+  and \texttt{EXEC}\nscodeidxdef{Test!Directive}{EXEC} is the following.
   \begin{code}
     EXECNOW: [ [ LOG file | BIN file ] ... ] cmd
   \end{code}
@@ -1052,7 +1057,7 @@ or
   the command is put in a test configuration file:
   \texttt{EXECNOW} executes the command only once for the test suite, while
   \texttt{EXEC} executes it once per test file of the test suite.
-\item The \texttt{MACRO}\sscodeidxdef{Test}{Directive}{MACRO} directive
+\item The \texttt{MACRO}\nscodeidx{Test!Directive}{MACRO} directive
   has the following syntax:
   \begin{code}
     MACRO: macro-name content
@@ -1063,8 +1068,15 @@ or
   \texttt{@macro-name@} in a \texttt{CMD}, \texttt{LOG}, \texttt{OPT},
   \texttt{STDOPT} or \texttt{EXECNOW} directive at this configuration level
   or in any level below it will be replaced by \texttt{content}. Existing
-  pre-defined macros are listed in section~\ref{sec:ptests-macros}. 
-\item The \texttt{FILEREG}\sscodeidxdef{Test}{Directive}{FILEREG}
+  pre-defined macros are listed in section~\ref{sec:ptests-macros}.
+\item \texttt{MODULE}\nscodeidx{Test!Directive}{MODULE}
+ directive takes as argument the name of a \texttt{.cmxs}
+  module. It will then add a directive to compile this file with the
+  command \texttt{@PTEST\_MAKE\_MODULE@ <MODULE>} where
+  \texttt{@PTEST\_MAKE\_MODULE@} defaults to \texttt{make -s}. Option
+  \texttt{-load-module <MODULE>} will then be appended to any subsequent Frama-C
+  command triggered by the test.
+\item The \texttt{FILEREG}\nscodeidx{Test!Directive}{FILEREG}
   directive contains a regular expression indicating which files in
   the directory containing the current test suite are actually part of
   the suite. This directive is only
diff --git a/doc/developer/macros.sty b/doc/developer/macros.sty
index bb199c65f158e989e393fe60dc336fbc1a9c5ee1..6a900ed9b7e46e3fa59deceef31ceba2c7a31d3b 100644
--- a/doc/developer/macros.sty
+++ b/doc/developer/macros.sty
@@ -30,7 +30,8 @@
   \index{#1@\texttt{#1}!#2@\texttt{\fontsize{8}{10}\selectfont #2}|bfit}}
 \newcommand{\sscodeidxdef}[3]{%
   \index{#1@\texttt{#1}!#2@\texttt{#2}!#3@\texttt{#3}|bfit}}
-
+\newcommand{\nscodeidx}[2]{\index{#1!#2@\texttt{#2}}}
+\newcommand{\nscodeidxdef}[2]{\index{#1!#2@\texttt{#2}|bfit}}
 \makeatletter
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/ptests/ptests.ml b/ptests/ptests.ml
index d99f688a4ac700f140617be5cb866a6d4711002d..79e513c75a36ad5d556520868c82ef411e778ad7 100644
--- a/ptests/ptests.ml
+++ b/ptests/ptests.ml
@@ -493,8 +493,6 @@ end = struct
 
 end
 
-let macro_regex = Str.regexp "\\([^@]*\\)@\\([^@]*\\)@\\(.*\\)"
-
 type execnow =
     {
       ex_cmd: string;      (** command to launch *)
@@ -510,7 +508,77 @@ type execnow =
                                are duplicated using OCaml 'with' syntax. *)
     }
 
-module StringMap = Map.Make(String)
+
+module Macros =
+struct
+  module StringMap = Map.Make (String)
+  open StringMap
+
+  type t = string StringMap.t
+
+  let empty = StringMap.empty
+
+  let macro_regex = Str.regexp "\\([^@]*\\)@\\([^@]*\\)@\\(.*\\)"
+
+  let does_expand macros s =
+    if !verbosity >=2 then begin
+      lock_printf "looking for macros in string %s\n%!" s;
+      lock_printf "Existing macros:\n%!";
+      iter (fun s1 s2 -> lock_printf "%s => %s\n%!" s1 s2) macros;
+      lock_printf "End macros\n%!";
+    end;
+    let rec aux n (ptest_file_matched,s as acc) =
+      if Str.string_match macro_regex s n then begin
+        let macro = Str.matched_group 2 s in
+        let ptest_file_matched = ptest_file_matched || macro = "PTEST_FILE" in
+        let start = Str.matched_group 1 s in
+        let rest = Str.matched_group 3 s in
+        let new_n = Str.group_end 1 in
+        let n, new_s =
+          if macro = "" then begin
+            new_n + 1, String.sub s 0 new_n ^ "@" ^ rest
+          end else begin
+            try
+              if !verbosity >= 2 then lock_printf "macro is %s\n%!" macro;
+              let replacement =  find macro macros in
+              if !verbosity >= 1 then
+                lock_printf "replacement for %s is %s\n%!" macro replacement;
+              new_n,
+              String.sub s 0 n ^ start ^ replacement ^ rest
+            with
+            | Not_found -> Str.group_end 2 + 1, s
+          end
+        in
+        if !verbosity >= 2 then lock_printf "new string is %s\n%!" new_s;
+        let new_acc = ptest_file_matched, new_s in
+        if n <= String.length new_s then aux n new_acc else new_acc
+      end else acc
+    in
+    Mutex.lock str_mutex;
+    try
+      let res = aux 0 (false,s) in
+      Mutex.unlock str_mutex; res
+    with e ->
+      lock_eprintf "Uncaught exception %s\n%!" (Printexc.to_string e);
+      Mutex.unlock str_mutex;
+      raise e
+
+  let expand macros s =
+    snd (does_expand macros s)
+
+  let get ?(default="") name macros =
+    try find name macros with Not_found -> default
+
+  let add_list l map =
+    List.fold_left (fun acc (k,v) -> add k v acc) map l
+
+  let add_expand name def macros =
+    add name (expand macros def) macros
+
+  let append_expand name def macros =
+    add name (get name macros ^ expand macros def) macros
+end
+
 
 (** configuration of a directory/test. *)
 type config =
@@ -519,7 +587,7 @@ type config =
       dc_execnow    : execnow list; (** command to be launched before
                                          the toplevel(s)
                                      *)
-      dc_macros: string StringMap.t; (** existing macros. *)
+      dc_macros: Macros.t; (** existing macros. *)
       dc_default_toplevel   : string;
       (** full path of the default toplevel. *)
       dc_filter     : string option; (** optional filter to apply to
@@ -532,7 +600,11 @@ type config =
     }
 
 let default_macros () =
-  StringMap.add "frama-c" !toplevel_path StringMap.empty
+  let l = [
+    "frama-c", !toplevel_path;
+    "PTEST_MAKE_MODULE", "make -s"
+  ] in
+  Macros.add_list l Macros.empty
 
 let default_config () =
   { dc_test_regexp = test_file_regexp ;
@@ -570,48 +642,6 @@ let launch command_string =
         s command_string;
       exit 1
 
-let replace_macros macros s =
-  if !verbosity >=2 then begin
-    lock_printf "looking for macros in string %s\n%!" s;
-    lock_printf "Existing macros:\n%!";
-    StringMap.iter (fun s1 s2 -> lock_printf "%s => %s\n%!" s1 s2) macros;
-    lock_printf "End macros\n%!";
-  end;
-  let rec aux n (ptest_file_matched,s as acc) =
-    if Str.string_match macro_regex s n then begin
-      let macro = Str.matched_group 2 s in
-      let ptest_file_matched = ptest_file_matched || macro = "PTEST_FILE" in
-      let start = Str.matched_group 1 s in
-      let rest = Str.matched_group 3 s in
-      let new_n = Str.group_end 1 in
-      let n, new_s =
-        if macro = "" then begin
-          new_n + 1, String.sub s 0 new_n ^ "@" ^ rest
-        end else begin
-          try
-            if !verbosity >= 2 then lock_printf "macro is %s\n%!" macro;
-            let replacement =  StringMap.find macro macros in
-            if !verbosity >= 1 then
-              lock_printf "replacement for %s is %s\n%!" macro replacement;
-            new_n,
-            String.sub s 0 n ^ start ^ replacement ^ rest
-          with
-          | Not_found -> Str.group_end 2 + 1, s
-        end
-      in
-       if !verbosity >= 2 then lock_printf "new string is %s\n%!" new_s;
-      let new_acc = ptest_file_matched, new_s in
-      if n <= String.length new_s then aux n new_acc else new_acc
-    end else acc
-  in
-  Mutex.lock str_mutex;
-  try
-    let res = aux 0 (false,s) in
-    Mutex.unlock str_mutex; res
-  with e ->
-    lock_eprintf "Uncaught exception %s\n%!" (Printexc.to_string e);
-    Mutex.unlock str_mutex;
-    raise e
 
 let scan_execnow ~once dir (s:string) =
   let rec aux (s:execnow) =
@@ -675,7 +705,12 @@ let make_custom_opts =
     (* preserve options ordering *)
     List.fold_right (fun x s -> s ^ " " ^ x) opts ""
 
-let add_macro s macros =
+
+(* how to process options *)
+let config_exec ~once dir s current =
+  { current with dc_execnow = scan_execnow ~once dir s :: current.dc_execnow }
+
+let config_macro _dir s current =
   let regex = Str.regexp "[ \t]*\\([^ \t@]+\\)\\([ \t]+\\(.*\\)\\|$\\)" in
   Mutex.lock str_mutex;
   if Str.string_match regex s 0 then begin
@@ -686,14 +721,20 @@ let add_macro s macros =
     Mutex.unlock str_mutex;
     if !verbosity >= 1 then
       lock_printf "new macro %s with definition %s\n%!" name def;
-    StringMap.add name (snd (replace_macros macros def)) macros
+    { current with dc_macros = Macros.add_expand name def current.dc_macros }
   end else begin
     Mutex.unlock str_mutex;
     lock_eprintf "cannot understand MACRO definition: %s\n%!" s;
-    macros
+    current
   end
 
-(* how to process options *)
+let config_module dir s current =
+  let make_cmd = "@PTEST_MAKE_MODULE@ " ^ s in
+  let make_cmd = Macros.expand current.dc_macros make_cmd in
+  let current = config_exec ~once:true dir make_cmd current in
+  let k = "PTEST_LOAD_MODULES" and v = " -load-module " ^ s in
+  { current with dc_macros = Macros.append_expand k v current.dc_macros }
+
 let config_options =
   [ "CMD",
     (fun _ s current -> 
@@ -733,21 +774,14 @@ let config_options =
     "DONTRUN",
     (fun _ s current -> { current with dc_dont_run = true });
 
-    "EXECNOW",
-    (fun dir s current ->
-      let execnow = scan_execnow ~once:true dir s in
-      { current with dc_execnow = execnow::current.dc_execnow  });
-
-    "EXEC",
-    (fun dir s current ->
-      let execnow = scan_execnow ~once:false dir s in
-      { current with dc_execnow = execnow::current.dc_execnow  });
-
-    "MACRO", (fun _ s current ->
-      { current with dc_macros = add_macro s current.dc_macros });
+    "EXECNOW", config_exec ~once:true;
+    "EXEC", config_exec ~once:false;
+    "MACRO", config_macro;
+    "MODULE", config_module;
     "LOG",
     (fun _ s current ->
      { current with dc_default_log = s :: current.dc_default_log })
+
   ]
 
 let scan_options dir scan_buffer default =
@@ -825,7 +859,7 @@ let scan_test_file default dir f =
       { default with dc_dont_run = true }
 
 type toplevel_command =
-    { macros: string StringMap.t;
+    { macros: Macros.t;
       mutable log_files: string list;
       file : string ;
       nb_files : int ;
@@ -934,9 +968,7 @@ let get_macros cmd =
       "PTEST_NUMBER", string_of_int cmd.n;
     ]
   in
-  List.fold_left
-    (fun acc (macro,replace) -> StringMap.add macro replace acc)
-    cmd.macros macros
+  Macros.add_list macros cmd.macros
 
 let basic_command_string =
   let contains_toplevel_or_frama_c = 
@@ -944,22 +976,19 @@ let basic_command_string =
   in
   fun command ->
     let macros = get_macros command in
-    let logfiles =
-      List.fold_left
-        (fun acc s -> snd (replace_macros macros s) :: acc)
-        []
-        command.log_files
-    in
+    let logfiles = List.map (Macros.expand macros) command.log_files in
     command.log_files <- logfiles;
-    let has_ptest_file_t, toplevel = replace_macros macros command.toplevel in
-    let has_ptest_file_o, options = replace_macros macros command.options in
+    let has_ptest_file_t, toplevel = Macros.does_expand macros command.toplevel in
+    let has_ptest_file_o, options = Macros.does_expand macros command.options in
     let toplevel = if !use_byte then opt_to_byte toplevel else toplevel in
     let options =
       if str_string_match contains_toplevel_or_frama_c command.toplevel 0
       then begin
-        let opt_pre = snd (replace_macros macros !additional_options_pre) in
-        let opt_post = snd (replace_macros macros !additional_options) in
-        "-check " ^ opt_pre ^ " " ^ options ^ " " ^ opt_post
+        let opt_modules = Macros.expand macros
+            (Macros.get "PTEST_LOAD_MODULES" macros) in
+        let opt_pre = Macros.expand macros !additional_options_pre in
+        let opt_post = Macros.expand macros !additional_options in
+        "-check " ^ opt_modules ^ " " ^ opt_pre ^ " " ^ options ^ " " ^ opt_post
       end else options
     in
     let options = if !use_byte then opt_to_byte_options options else options in
@@ -1083,11 +1112,7 @@ let update_toplevel_command command =
     Unix.Unix_error _ -> ()
   end;
   let macros = get_macros command in
-  let log_files =
-    List.fold_left
-      (fun acc s -> snd (replace_macros macros s) :: acc)
-      []
-      command.log_files
+  let log_files = List.map (Macros.expand macros) command.log_files
   in
   List.iter (update_log_files command.directory) log_files
 
@@ -1605,7 +1630,7 @@ let dispatcher () =
             macros = config.dc_macros;
             execnow = true; }
       in
-      let process_macros s = snd (replace_macros config.dc_macros s) in
+      let process_macros s = Macros.expand config.dc_macros s in
       let make_execnow_cmd execnow =
         let res =
           {
diff --git a/tests/misc/behavior_names.i b/tests/misc/behavior_names.i
index d4e48cb5bf64a0462aa4b9196b3d1dd23f533b9a..20a66a894482270937c3af919cdc95bcc68f1a90 100644
--- a/tests/misc/behavior_names.i
+++ b/tests/misc/behavior_names.i
@@ -1,6 +1,6 @@
 /* run.config
-   EXECNOW: make -s @PTEST_DIR@/@PTEST_NAME@.cmxs
-   OPT: -no-autoload-plugins -load-module @PTEST_DIR@/@PTEST_NAME@.cmxs
+   MODULE: @PTEST_DIR@/@PTEST_NAME@.cmxs
+   OPT: -no-autoload-plugins
 */
 
 /*@ behavior foo: ensures \true; */
diff --git a/tests/misc/debug_category.i b/tests/misc/debug_category.i
index 4773466c671722c22fdb1b53992f6073349b9383..5646776c8be6e08df7545b9b872b067995932fa5 100644
--- a/tests/misc/debug_category.i
+++ b/tests/misc/debug_category.i
@@ -1,21 +1,21 @@
 /* run.config
-EXECNOW: make -s tests/misc/Debug_category.cmxs
-OPT: -load-module tests/misc/Debug_category.cmxs -test-msg-key help -test-warn-key="a=inactive"
-OPT: -load-module tests/misc/Debug_category.cmxs -test-msg-key a -test-warn-key="a=inactive"
-OPT: -load-module tests/misc/Debug_category.cmxs -test-msg-key a -test-msg-key="-a:b" -test-warn-key="a=inactive"
-OPT: -load-module tests/misc/Debug_category.cmxs -test-msg-key a -test-msg-key="-a:b" -test-msg-key a:b:c -test-warn-key="a=inactive"
-OPT: -load-module tests/misc/Debug_category.cmxs -test-msg-key "a:b:c,d" -test-warn-key="a=inactive"
-OPT: -load-module tests/misc/Debug_category.cmxs -test-msg-key "*" -test-warn-key="a=inactive"
-OPT: -load-module tests/misc/Debug_category.cmxs
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key a=error
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key a=abort
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key a=feedback
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key="*=abort"
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key=a=once
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key a=feedback-once
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key a=err-once
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key test-vis-err
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key test-inv-err
-OPT: -load-module tests/misc/Debug_category.cmxs -test-warn-key test-failure
+MODULE: tests/misc/Debug_category.cmxs
+OPT: -test-msg-key help -test-warn-key="a=inactive"
+OPT: -test-msg-key a -test-warn-key="a=inactive"
+OPT: -test-msg-key a -test-msg-key="-a:b" -test-warn-key="a=inactive"
+OPT: -test-msg-key a -test-msg-key="-a:b" -test-msg-key a:b:c -test-warn-key="a=inactive"
+OPT: -test-msg-key "a:b:c,d" -test-warn-key="a=inactive"
+OPT: -test-msg-key "*" -test-warn-key="a=inactive"
+OPT:
+OPT: -test-warn-key a=error
+OPT: -test-warn-key a=abort
+OPT: -test-warn-key a=feedback
+OPT: -test-warn-key="*=abort"
+OPT: -test-warn-key=a=once
+OPT: -test-warn-key a=feedback-once
+OPT: -test-warn-key a=err-once
+OPT: -test-warn-key test-vis-err
+OPT: -test-warn-key test-inv-err
+OPT: -test-warn-key test-failure
 FILTER: sed 's|Your Frama-C version is.*|Your Frama-C version is VERSION|'
 */