aboutsummaryrefslogtreecommitdiffstats
path: root/erts/test/erlc_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'erts/test/erlc_SUITE.erl')
-rw-r--r--erts/test/erlc_SUITE.erl397
1 files changed, 235 insertions, 162 deletions
diff --git a/erts/test/erlc_SUITE.erl b/erts/test/erlc_SUITE.erl
index ab774dbc4f..237558a129 100644
--- a/erts/test/erlc_SUITE.erl
+++ b/erts/test/erlc_SUITE.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
%%
%% %CopyrightEnd%
%%
@@ -21,17 +22,18 @@
%% Tests the erlc command by compiling various types of files.
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2, compile_erl/1,
- compile_yecc/1, compile_script/1,
- compile_mib/1, good_citizen/1, deep_cwd/1, arg_overflow/1]).
+ init_per_group/2,end_per_group/2, compile_erl/1,
+ compile_yecc/1, compile_script/1,
+ compile_mib/1, good_citizen/1, deep_cwd/1, arg_overflow/1,
+ make_dep_options/1]).
--include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[compile_erl, compile_yecc, compile_script, compile_mib,
- good_citizen, deep_cwd, arg_overflow].
+ good_citizen, deep_cwd, arg_overflow, make_dep_options].
groups() ->
[].
@@ -55,113 +57,109 @@ end_per_group(_GroupName, Config) ->
%% Tests that compiling Erlang source code works.
compile_erl(Config) when is_list(Config) ->
- ?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
- ?line FileName = filename:join(SrcDir, "erl_test_ok.erl"),
+ {SrcDir, OutDir, Cmd} = get_cmd(Config),
+ FileName = filename:join(SrcDir, "erl_test_ok.erl"),
%% By default, warnings are now turned on.
- ?line run(Config, Cmd, FileName, "",
- ["Warning: function foo/0 is unused\$",
- "_OK_"]),
+ run(Config, Cmd, FileName, "",
+ ["Warning: function foo/0 is unused\$", "_OK_"]),
%% Test that the compiled file is where it should be,
%% and that it is runnable.
- ?line {module, erl_test_ok} = code:load_abs(filename:join(OutDir,
- "erl_test_ok")),
- ?line 42 = erl_test_ok:shoe_size(#person{shoe_size=42}),
- ?line code:purge(erl_test_ok),
+ {module, erl_test_ok} = code:load_abs(filename:join(OutDir, "erl_test_ok")),
+ 42 = erl_test_ok:shoe_size(#person{shoe_size=42}),
+ code:purge(erl_test_ok),
%% Try disabling warnings.
- ?line run(Config, Cmd, FileName, "-W0", ["_OK_"]),
+ run(Config, Cmd, FileName, "-W0", ["_OK_"]),
%% Try treating warnings as errors.
- ?line run(Config, Cmd, FileName, "-Werror",
- ["compile: warnings being treated as errors\$",
- "function foo/0 is unused\$",
- "_ERROR_"]),
+ run(Config, Cmd, FileName, "-Werror",
+ ["compile: warnings being treated as errors\$",
+ "function foo/0 is unused\$", "_ERROR_"]),
%% Check a bad file.
- ?line BadFile = filename:join(SrcDir, "erl_test_bad.erl"),
- ?line run(Config, Cmd, BadFile, "", ["function non_existing/1 undefined\$",
- "_ERROR_"]),
+ BadFile = filename:join(SrcDir, "erl_test_bad.erl"),
+ run(Config, Cmd, BadFile, "", ["function non_existing/1 undefined\$",
+ "_ERROR_"]),
ok.
%% Test that compiling yecc source code works.
compile_yecc(Config) when is_list(Config) ->
- ?line {SrcDir, _, OutDir} = get_dirs(Config),
- ?line Cmd = erlc() ++ " -o" ++ OutDir ++ " ",
- ?line FileName = filename:join(SrcDir, "yecc_test_ok.yrl"),
- ?line run(Config, Cmd, FileName, "-W0", ["_OK_"]),
- ?line true = exists(filename:join(OutDir, "yecc_test_ok.erl")),
-
- ?line BadFile = filename:join(SrcDir, "yecc_test_bad.yrl"),
- ?line run(Config, Cmd, BadFile, "-W0",
- ["rootsymbol form is not a nonterminal\$",
- "undefined nonterminal: form\$",
- "Nonterminals is missing\$",
- "_ERROR_"]),
- ?line exists(filename:join(OutDir, "yecc_test_ok.erl")),
-
+ {SrcDir, _, OutDir} = get_dirs(Config),
+ Cmd = erlc() ++ " -o" ++ OutDir ++ " ",
+ FileName = filename:join(SrcDir, "yecc_test_ok.yrl"),
+ run(Config, Cmd, FileName, "-W0", ["_OK_"]),
+ true = exists(filename:join(OutDir, "yecc_test_ok.erl")),
+
+ BadFile = filename:join(SrcDir, "yecc_test_bad.yrl"),
+ run(Config, Cmd, BadFile, "-W0",
+ ["rootsymbol form is not a nonterminal\$",
+ "undefined nonterminal: form\$",
+ "Nonterminals is missing\$",
+ "_ERROR_"]),
+ exists(filename:join(OutDir, "yecc_test_ok.erl")),
ok.
%% Test that compiling start scripts works.
compile_script(Config) when is_list(Config) ->
- ?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
- ?line FileName = filename:join(SrcDir, "start_ok.script"),
- ?line run(Config, Cmd, FileName, "", ["_OK_"]),
- ?line true = exists(filename:join(OutDir, "start_ok.boot")),
+ {SrcDir, OutDir, Cmd} = get_cmd(Config),
+ FileName = filename:join(SrcDir, "start_ok.script"),
+ run(Config, Cmd, FileName, "", ["_OK_"]),
+ true = exists(filename:join(OutDir, "start_ok.boot")),
- ?line BadFile = filename:join(SrcDir, "start_bad.script"),
- ?line run(Config, Cmd, BadFile, "", ["syntax error before:", "_ERROR_"]),
+ BadFile = filename:join(SrcDir, "start_bad.script"),
+ run(Config, Cmd, BadFile, "", ["syntax error before:", "_ERROR_"]),
ok.
%% Test that compiling SNMP mibs works.
compile_mib(Config) when is_list(Config) ->
- ?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
- ?line FileName = filename:join(SrcDir, "GOOD-MIB.mib"),
- ?line run(Config, Cmd, FileName, "", ["_OK_"]),
- ?line Output = filename:join(OutDir, "GOOD-MIB.bin"),
- ?line true = exists(Output),
+ {SrcDir, OutDir, Cmd} = get_cmd(Config),
+ FileName = filename:join(SrcDir, "GOOD-MIB.mib"),
+ run(Config, Cmd, FileName, "", ["_OK_"]),
+ Output = filename:join(OutDir, "GOOD-MIB.bin"),
+ true = exists(Output),
%% Try -W option.
- ?line ok = file:delete(Output),
- ?line run(Config, Cmd, FileName, "-W",
- ["_OK_"]),
- ?line true = exists(Output),
+ ok = file:delete(Output),
+ run(Config, Cmd, FileName, "-W",
+ ["_OK_"]),
+ true = exists(Output),
%% Try -W option and more verbose.
- ?line ok = file:delete(Output),
- ?line case test_server:os_type() of
- {unix,_} ->
- ?line run(Config, Cmd, FileName, "-W +'{verbosity,info}'",
- ["\\[GOOD-MIB[.]mib\\]\\[INF\\]: No accessfunction for 'sysDescr' => using default",
- "_OK_"]),
- ?line true = exists(Output),
- ?line ok = file:delete(Output);
- _ -> ok %Don't bother -- too much work.
- end,
+ ok = file:delete(Output),
+ case test_server:os_type() of
+ {unix,_} ->
+ run(Config, Cmd, FileName, "-W +'{verbosity,info}'",
+ ["\\[GOOD-MIB[.]mib\\]\\[INF\\]: No accessfunction for 'sysDescr' => using default",
+ "_OK_"]),
+ true = exists(Output),
+ ok = file:delete(Output);
+ _ -> ok %Don't bother -- too much work.
+ end,
%% Try a bad file.
- ?line BadFile = filename:join(SrcDir, "BAD-MIB.mib"),
- ?line run(Config, Cmd, BadFile, "",
- ["BAD-MIB.mib: 1: syntax error before: mibs\$",
- "compilation_failed_ERROR_"]),
+ BadFile = filename:join(SrcDir, "BAD-MIB.mib"),
+ run(Config, Cmd, BadFile, "",
+ ["BAD-MIB.mib: 1: syntax error before: mibs\$",
+ "compilation_failed_ERROR_"]),
%% Make sure that no -I option works.
- ?line NewCmd = erlc() ++ " -o" ++ OutDir ++ " ",
- ?line run(Config, NewCmd, FileName, "", ["_OK_"]),
- ?line true = exists(Output),
+ NewCmd = erlc() ++ " -o" ++ OutDir ++ " ",
+ run(Config, NewCmd, FileName, "", ["_OK_"]),
+ true = exists(Output),
ok.
@@ -169,99 +167,175 @@ compile_mib(Config) when is_list(Config) ->
%% shell script with redirected input).
good_citizen(Config) when is_list(Config) ->
case os:type() of
- {unix, _} ->
- ?line PrivDir = ?config(priv_dir, Config),
- ?line Answer = filename:join(PrivDir, "answer"),
- ?line Script = filename:join(PrivDir, "test_script"),
- ?line Test = filename:join(PrivDir, "test.erl"),
- ?line S = ["#! /bin/sh\n", "erlc ", Test, "\n",
- "read reply\n", "echo $reply\n"],
- ?line ok = file:write_file(Script, S),
- ?line ok = file:write_file(Test, "-module(test).\n"),
- ?line Cmd = "echo y | sh " ++ Script ++ " > " ++ Answer,
- ?line os:cmd(Cmd),
- ?line {ok, Answer0} = file:read_file(Answer),
- ?line [$y|_] = binary_to_list(Answer0),
- ok;
- _ ->
- {skip, "Unix specific"}
+ {unix, _} ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Answer = filename:join(PrivDir, "answer"),
+ Script = filename:join(PrivDir, "test_script"),
+ Test = filename:join(PrivDir, "test.erl"),
+ S = ["#! /bin/sh\n", "erlc ", Test, "\n",
+ "read reply\n", "echo $reply\n"],
+ ok = file:write_file(Script, S),
+ ok = file:write_file(Test, "-module(test).\n"),
+ Cmd = "echo y | sh " ++ Script ++ " > " ++ Answer,
+ os:cmd(Cmd),
+ {ok, Answer0} = file:read_file(Answer),
+ [$y|_] = binary_to_list(Answer0),
+ ok;
+ _ ->
+ {skip, "Unix specific"}
end.
%% Make sure that compiling an Erlang module deep down in
%% in a directory with more than 255 characters works.
deep_cwd(Config) when is_list(Config) ->
case os:type() of
- {unix, _} ->
- PrivDir = ?config(priv_dir, Config),
- deep_cwd_1(PrivDir);
- _ ->
- {skip, "Only a problem on Unix"}
+ {unix, _} ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ deep_cwd_1(PrivDir);
+ _ ->
+ {skip, "Only a problem on Unix"}
end.
deep_cwd_1(PrivDir) ->
- ?line DeepDir0 = filename:join(PrivDir, lists:duplicate(128, $a)),
- ?line DeepDir = filename:join(DeepDir0, lists:duplicate(128, $b)),
- ?line ok = file:make_dir(DeepDir0),
- ?line ok = file:make_dir(DeepDir),
- ?line ok = file:set_cwd(DeepDir),
- ?line ok = file:write_file("test.erl", "-module(test).\n\n"),
- ?line io:format("~s\n", [os:cmd("erlc test.erl")]),
- ?line true = filelib:is_file("test.beam"),
+ DeepDir0 = filename:join(PrivDir, lists:duplicate(128, $a)),
+ DeepDir = filename:join(DeepDir0, lists:duplicate(128, $b)),
+ ok = file:make_dir(DeepDir0),
+ ok = file:make_dir(DeepDir),
+ ok = file:set_cwd(DeepDir),
+ ok = file:write_file("test.erl", "-module(test).\n\n"),
+ io:format("~s\n", [os:cmd("erlc test.erl")]),
+ true = filelib:is_file("test.beam"),
ok.
%% Test that a large number of command line switches does not
%% overflow the argument buffer
arg_overflow(Config) when is_list(Config) ->
- ?line {SrcDir, _OutDir, Cmd} = get_cmd(Config),
- ?line FileName = filename:join(SrcDir, "erl_test_ok.erl"),
+ {SrcDir, _OutDir, Cmd} = get_cmd(Config),
+ FileName = filename:join(SrcDir, "erl_test_ok.erl"),
%% Each -D option will be expanded to three arguments when
%% invoking 'erl'.
- ?line NumDOptions = num_d_options(),
- ?line Args = lists:flatten([ ["-D", integer_to_list(N, 36), "=1 "] ||
- N <- lists:seq(1, NumDOptions) ]),
- ?line run(Config, Cmd, FileName, Args,
- ["Warning: function foo/0 is unused\$",
- "_OK_"]),
+ NumDOptions = num_d_options(),
+ Args = lists:flatten([ ["-D", integer_to_list(N, 36), "=1 "] ||
+ N <- lists:seq(1, NumDOptions) ]),
+ run(Config, Cmd, FileName, Args,
+ ["Warning: function foo/0 is unused\$",
+ "_OK_"]),
ok.
num_d_options() ->
case {os:type(),os:version()} of
- {{win32,_},_} ->
- %% The maximum size of a command line in the command
- %% shell on Windows is 8191 characters.
- %% Each -D option is expanded to "@dv NN 1", i.e.
- %% 8 characters. (Numbers up to 1295 can be expressed
- %% as two 36-base digits.)
- 1000;
- {{unix,linux},Version} when Version < {2,6,23} ->
- %% On some older 64-bit versions of Linux, the maximum number
- %% of arguments is 16383.
- %% See: http://www.in-ulm.de/~mascheck/various/argmax/
- 5440;
- {{unix,darwin},{Major,_,_}} when Major >= 11 ->
- %% "getconf ARG_MAX" still reports 262144 (as in previous
- %% version of MacOS X), but the useful space seem to have
- %% shrunk significantly (or possibly the number of arguments).
- %% 7673
- 7500;
- {_,_} ->
- 12000
+ {{win32,_},_} ->
+ %% The maximum size of a command line in the command
+ %% shell on Windows is 8191 characters.
+ %% Each -D option is expanded to "@dv NN 1", i.e.
+ %% 8 characters. (Numbers up to 1295 can be expressed
+ %% as two 36-base digits.)
+ 1000;
+ {{unix,linux},Version} when Version < {2,6,23} ->
+ %% On some older 64-bit versions of Linux, the maximum number
+ %% of arguments is 16383.
+ %% See: http://www.in-ulm.de/~mascheck/various/argmax/
+ 5440;
+ {{unix,darwin},{Major,_,_}} when Major >= 11 ->
+ %% "getconf ARG_MAX" still reports 262144 (as in previous
+ %% version of MacOS X), but the useful space seem to have
+ %% shrunk significantly (or possibly the number of arguments).
+ %% 7673
+ 7500;
+ {_,_} ->
+ 12000
end.
erlc() ->
case os:find_executable("erlc") of
- false ->
- test_server:fail("Can't find erlc");
- Erlc ->
- "\"" ++ Erlc ++ "\""
+ false ->
+ ct:fail("Can't find erlc");
+ Erlc ->
+ "\"" ++ Erlc ++ "\""
end.
-
+
+make_dep_options(Config) ->
+ {SrcDir,OutDir,Cmd} = get_cmd(Config),
+ FileName = filename:join(SrcDir, "erl_test_ok.erl"),
+
+
+ DepRE = ["/erl_test_ok[.]beam: \\\\$",
+ "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$",
+ "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$",
+ "_OK_"],
+
+ DepRETarget =
+ ["^target: \\\\$",
+ "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$",
+ "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$",
+ "_OK_"],
+
+ DepREMP =
+ ["/erl_test_ok[.]beam: \\\\$",
+ "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$",
+ "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$",
+ [],
+ "/system_test/erlc_SUITE_data/include/erl_test.hrl:$",
+ "_OK_"],
+
+ DepREMissing =
+ ["/erl_test_missing_header[.]beam: \\\\$",
+ "/system_test/erlc_SUITE_data/src/erl_test_missing_header[.]erl \\\\$",
+ "/system_test/erlc_SUITE_data/include/erl_test[.]hrl \\\\$",
+ "missing.hrl$",
+ "_OK_"],
+
+ %% Test plain -M
+ run(Config, Cmd, FileName, "-M", DepRE),
+
+ %% Test -MF File
+ DepFile = filename:join(OutDir, "my.deps"),
+ run(Config, Cmd, FileName, "-MF "++DepFile, ["_OK_"]),
+ {ok,MFBin} = file:read_file(DepFile),
+ verify_result(binary_to_list(MFBin)++["_OK_"], DepRE),
+
+ %% Test -MD
+ run(Config, Cmd, FileName, "-MD", ["_OK_"]),
+ MDFile = filename:join(OutDir, "erl_test_ok.Pbeam"),
+ {ok,MFBin} = file:read_file(MDFile),
+
+ %% Test -M -MT Target
+ run(Config, Cmd, FileName, "-M -MT target", DepRETarget),
+
+ %% Test -MF File -MT Target
+ TargetDepFile = filename:join(OutDir, "target.deps"),
+ run(Config, Cmd, FileName, "-MF "++TargetDepFile++" -MT target",
+ ["_OK_"]),
+ {ok,TargetBin} = file:read_file(TargetDepFile),
+ verify_result(binary_to_list(TargetBin)++["_OK_"], DepRETarget),
+
+ %% Test -MD -MT Target
+ run(Config, Cmd, FileName, "-MD -MT target", ["_OK_"]),
+ TargetMDFile = filename:join(OutDir, "erl_test_ok.Pbeam"),
+ {ok,TargetBin} = file:read_file(TargetMDFile),
+
+ %% Test -M -MQ Target. (Note: Passing a $ on the command line
+ %% portably for Unix and Windows is tricky, so we will just test
+ %% that MQ works at all.)
+ run(Config, Cmd, FileName, "-M -MQ target", DepRETarget),
+
+ %% Test -M -MP
+ run(Config, Cmd, FileName, "-M -MP", DepREMP),
+
+ %% Test -M -MG
+ MissingHeader = filename:join(SrcDir, "erl_test_missing_header.erl"),
+ run(Config, Cmd, MissingHeader, "-M -MG", DepREMissing),
+ ok.
+
%% Runs a command.
run(Config, Cmd0, Name, Options, Expect) ->
Cmd = Cmd0 ++ " " ++ Options ++ " " ++ Name,
- io:format("~s", [Cmd]),
+ io:format("~ts", [Cmd]),
Result = run_command(Config, Cmd),
+ verify_result(Result, Expect).
+
+verify_result(Result, Expect) ->
Messages = split(Result, [], []),
io:format("Result: ~p", [Messages]),
io:format("Expected: ~p", [Expect]),
@@ -279,34 +353,34 @@ split([], Current, Lines) ->
split([], [], [lists:reverse(Current)|Lines]).
match_messages([Msg|Rest1], [Regexp|Rest2]) ->
- case re:run(Msg, Regexp, [{capture,none}]) of
- match ->
- ok;
- nomatch ->
- io:format("Not matching: ~s\n", [Msg]),
- io:format("Regexp : ~s\n", [Regexp]),
- test_server:fail(message_mismatch)
+ case re:run(Msg, Regexp, [{capture,none}, unicode]) of
+ match ->
+ ok;
+ nomatch ->
+ io:format("Not matching: ~s\n", [Msg]),
+ io:format("Regexp : ~s\n", [Regexp]),
+ ct:fail(message_mismatch)
end,
match_messages(Rest1, Rest2);
match_messages([], [Expect|Rest]) ->
- test_server:fail({too_few_messages, [Expect|Rest]});
+ ct:fail({too_few_messages, [Expect|Rest]});
match_messages([Msg|Rest], []) ->
- test_server:fail({too_many_messages, [Msg|Rest]});
+ ct:fail({too_many_messages, [Msg|Rest]});
match_messages([], []) ->
ok.
get_cmd(Cfg) ->
- ?line {SrcDir, IncDir, OutDir} = get_dirs(Cfg),
- ?line Cmd = erlc() ++ " -I" ++ IncDir ++ " -o" ++ OutDir ++ " ",
+ {SrcDir, IncDir, OutDir} = get_dirs(Cfg),
+ Cmd = erlc() ++ " -I" ++ IncDir ++ " -o" ++ OutDir ++ " ",
{SrcDir, OutDir, Cmd}.
get_dirs(Cfg) ->
- ?line DataDir = ?config(data_dir, Cfg),
- ?line PrivDir = ?config(priv_dir, Cfg),
- ?line SrcDir = filename:join(DataDir, "src"),
- ?line IncDir = filename:join(DataDir, "include"),
+ DataDir = proplists:get_value(data_dir, Cfg),
+ PrivDir = proplists:get_value(priv_dir, Cfg),
+ SrcDir = filename:join(DataDir, "src"),
+ IncDir = filename:join(DataDir, "include"),
{SrcDir, IncDir, PrivDir}.
-
+
exists(Name) ->
filelib:is_file(Name).
@@ -318,16 +392,16 @@ exists(Name) ->
%% a non-zero exit status.
run_command(Config, Cmd) ->
- TmpDir = filename:join(?config(priv_dir, Config), "tmp"),
+ TmpDir = filename:join(proplists:get_value(priv_dir, Config), "tmp"),
file:make_dir(TmpDir),
{RunFile, Run, Script} = run_command(TmpDir, os:type(), Cmd),
- ok = file:write_file(filename:join(TmpDir, RunFile), Script),
+ ok = file:write_file(filename:join(TmpDir, RunFile), unicode:characters_to_binary(Script)),
os:cmd(Run).
run_command(Dir, {win32, _}, Cmd) ->
BatchFile = filename:join(Dir, "run.bat"),
Run = re:replace(filename:rootname(BatchFile), "/", "\\",
- [global,{return,list}]),
+ [global,{return,list}]),
{BatchFile,
Run,
["@echo off\r\n",
@@ -348,5 +422,4 @@ run_command(Dir, {unix, _}, Cmd) ->
" *) echo '_ERROR_';;\n",
"esac\n"]};
run_command(_Dir, Other, _Cmd) ->
- M = io_lib:format("Don't know how to test exit code for ~p", [Other]),
- test_server:fail(lists:flatten(M)).
+ ct:fail("Don't know how to test exit code for ~p", [Other]).