aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/test/ssh_to_openssh_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/test/ssh_to_openssh_SUITE.erl')
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl427
1 files changed, 164 insertions, 263 deletions
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index b7283202a3..2788bc6b58 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -33,6 +33,9 @@
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{minutes,1}}].
+
all() ->
case os:find_executable("ssh") of
false ->
@@ -45,37 +48,26 @@ all() ->
groups() ->
[{erlang_client, [], [erlang_shell_client_openssh_server,
- erlang_client_openssh_server_exec,
erlang_client_openssh_server_exec_compressed,
erlang_client_openssh_server_setenv,
erlang_client_openssh_server_publickey_rsa,
erlang_client_openssh_server_publickey_dsa,
erlang_client_openssh_server_password,
+ erlang_client_openssh_server_kexs,
erlang_client_openssh_server_nonexistent_subsystem
]},
- {erlang_server, [], [erlang_server_openssh_client_exec,
- erlang_server_openssh_client_exec_compressed,
- erlang_server_openssh_client_pulic_key_dsa,
- erlang_server_openssh_client_cipher_suites,
- erlang_server_openssh_client_macs]}
+ {erlang_server, [], [erlang_server_openssh_client_public_key_dsa]}
].
init_per_suite(Config) ->
- catch crypto:stop(),
- case catch crypto:start() of
- ok ->
- case gen_tcp:connect("localhost", 22, []) of
- {error,econnrefused} ->
- {skip,"No openssh deamon"};
- _ ->
- ssh_test_lib:openssh_sanity_check(Config)
- end;
- _Else ->
- {skip,"Could not start crypto!"}
+ case gen_tcp:connect("localhost", 22, []) of
+ {error,econnrefused} ->
+ {skip,"No openssh deamon"};
+ _ ->
+ ssh_test_lib:openssh_sanity_check(Config)
end.
end_per_suite(_Config) ->
- crypto:stop(),
ok.
init_per_group(erlang_server, Config) ->
@@ -83,6 +75,11 @@ init_per_group(erlang_server, Config) ->
UserDir = ?config(priv_dir, Config),
ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
Config;
+init_per_group(erlang_client, Config) ->
+ CommonAlgs = ssh_test_lib:algo_intersection(
+ ssh:default_algorithms(),
+ ssh_test_lib:default_algorithms(sshd)),
+ [{common_algs,CommonAlgs} | Config];
init_per_group(_, Config) ->
Config.
@@ -93,12 +90,11 @@ end_per_group(erlang_server, Config) ->
end_per_group(_, Config) ->
Config.
-init_per_testcase(erlang_server_openssh_client_cipher_suites, Config) ->
- check_ssh_client_support(Config);
-
-init_per_testcase(erlang_server_openssh_client_macs, Config) ->
- check_ssh_client_support(Config);
+init_per_testcase(erlang_server_openssh_client_public_key_dsa, Config) ->
+ chk_key(sshc, 'ssh-dss', ".ssh/id_dsa", Config);
+init_per_testcase(erlang_client_openssh_server_publickey_dsa, Config) ->
+ chk_key(sshd, 'ssh-dss', ".ssh/id_dsa", Config);
init_per_testcase(_TestCase, Config) ->
ssh:start(),
Config.
@@ -107,6 +103,27 @@ end_per_testcase(_TestCase, _Config) ->
ssh:stop(),
ok.
+
+chk_key(Pgm, Name, File, Config) ->
+ case ssh_test_lib:openssh_supports(Pgm, public_key, Name) of
+ false ->
+ {skip,lists:concat(["openssh client does not support ",Name])};
+ true ->
+ {ok,[[Home]]} = init:get_argument(home),
+ KeyFile = filename:join(Home, File),
+ case file:read_file(KeyFile) of
+ {ok, Pem} ->
+ case public_key:pem_decode(Pem) of
+ [{_,_, not_encrypted}] ->
+ init_per_testcase('__default__',Config);
+ _ ->
+ {skip, {error, "Has pass phrase can not be used by automated test case"}}
+ end;
+ _ ->
+ {skip, lists:concat(["no ~/",File])}
+ end
+ end.
+
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -140,7 +157,7 @@ erlang_client_openssh_server_exec(Config) when is_list(Config) ->
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0);
{unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}}
= ExitStatus0} ->
- ct:pal("0: Collected data ~p", [ExitStatus0]),
+ ct:log("0: Collected data ~p", [ExitStatus0]),
ssh_test_lib:receive_exec_result(Data0,
ConnectionRef, ChannelId0);
Other0 ->
@@ -156,7 +173,7 @@ erlang_client_openssh_server_exec(Config) when is_list(Config) ->
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1);
{unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId1, 0}}
= ExitStatus1} ->
- ct:pal("0: Collected data ~p", [ExitStatus1]),
+ ct:log("0: Collected data ~p", [ExitStatus1]),
ssh_test_lib:receive_exec_result(Data1,
ConnectionRef, ChannelId1);
Other1 ->
@@ -169,188 +186,80 @@ erlang_client_openssh_server_exec_compressed() ->
erlang_client_openssh_server_exec_compressed(Config) when is_list(Config) ->
CompressAlgs = [zlib, '[email protected]',none],
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false},
- {preferred_algorithms,
- [{compression,CompressAlgs}]}]),
- {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
- success = ssh_connection:exec(ConnectionRef, ChannelId,
- "echo testing", infinity),
- Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}},
- case ssh_test_lib:receive_exec_result(Data) of
- expected ->
- ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId);
- {unexpected_msg,{ssh_cm, ConnectionRef,
- {exit_status, ChannelId, 0}} = ExitStatus} ->
- ct:pal("0: Collected data ~p", [ExitStatus]),
- ssh_test_lib:receive_exec_result(Data, ConnectionRef, ChannelId);
- Other ->
- ct:fail(Other)
+ case ssh_test_lib:ssh_supports(CompressAlgs, compression) of
+ {false,L} ->
+ {skip, io_lib:format("~p compression is not supported",[L])};
+
+ true ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {preferred_algorithms,
+ [{compression,CompressAlgs}]}]),
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId,
+ "echo testing", infinity),
+ Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}},
+ case ssh_test_lib:receive_exec_result(Data) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId);
+ {unexpected_msg,{ssh_cm, ConnectionRef,
+ {exit_status, ChannelId, 0}} = ExitStatus} ->
+ ct:log("0: Collected data ~p", [ExitStatus]),
+ ssh_test_lib:receive_exec_result(Data, ConnectionRef, ChannelId);
+ Other ->
+ ct:fail(Other)
+ end
end.
%%--------------------------------------------------------------------
-erlang_server_openssh_client_exec() ->
- [{doc, "Test that exec command works."}].
-
-erlang_server_openssh_client_exec(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
-
- ct:sleep(500),
-
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " 1+1.",
-
- ct:pal("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
-
- end,
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_cipher_suites() ->
- [{doc, "Test that we can connect with different cipher suites."}].
-
-erlang_server_openssh_client_cipher_suites(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
-
- ct:sleep(500),
-
- Supports = crypto:supports(),
- Ciphers = proplists:get_value(ciphers, Supports),
- Tests = [
- {"3des-cbc", lists:member(des3_cbc, Ciphers)},
- {"aes128-cbc", lists:member(aes_cbc128, Ciphers)},
- {"aes128-ctr", lists:member(aes_ctr, Ciphers)},
- {"aes256-cbc", false}
- ],
- lists:foreach(fun({Cipher, Expect}) ->
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " " ++
- " -c " ++ Cipher ++ " 1+1.",
-
- ct:pal("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- case Expect of
- true ->
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
- end;
- false ->
- receive
- {SshPort,{data, <<"no matching cipher found", _/binary>>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive no matching cipher message")
- end
- end
- end, Tests),
-
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_macs() ->
- [{doc, "Test that we can connect with different MACs."}].
-
-erlang_server_openssh_client_macs(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
-
- ct:sleep(500),
-
- Supports = crypto:supports(),
- Hashs = proplists:get_value(hashs, Supports),
- MACs = [{"hmac-sha1", lists:member(sha, Hashs)},
- {"hmac-sha2-256", lists:member(sha256, Hashs)},
- {"hmac-md5-96", false},
- {"hmac-ripemd160", false}],
- lists:foreach(fun({MAC, Expect}) ->
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " " ++
- " -o MACs=" ++ MAC ++ " 1+1.",
-
- ct:pal("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- case Expect of
- true ->
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
- end;
- false ->
- receive
- {SshPort,{data, <<"no matching mac found", _/binary>>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive no matching mac message")
- end
- end
- end, MACs),
-
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_exec_compressed() ->
- [{doc, "Test that exec command works."}].
-
-erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
-%% CompressAlgs = [zlib, '[email protected]'], % Does not work
- CompressAlgs = [zlib],
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {preferred_algorithms,
- [{compression, CompressAlgs}]},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
- ct:sleep(500),
-
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " -C "++ Host ++ " 1+1.",
- SshPort = open_port({spawn, Cmd}, [binary]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
-
- end,
- ssh:stop_daemon(Pid).
+erlang_client_openssh_server_kexs() ->
+ [{doc, "Test that we can connect with different KEXs."}].
+
+erlang_client_openssh_server_kexs(Config) when is_list(Config) ->
+ KexAlgos = try proplists:get_value(kex, ?config(common_algs,Config))
+ catch _:_ -> []
+ end,
+ comment(KexAlgos),
+ case KexAlgos of
+ [] -> {skip, "No common kex algorithms"};
+ _ ->
+ Success =
+ lists:foldl(
+ fun(Kex, Acc) ->
+ ConnectionRef =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {preferred_algorithms,
+ [{kex,[Kex]}]}]),
+
+ {ok, ChannelId} =
+ ssh_connection:session_channel(ConnectionRef, infinity),
+ success =
+ ssh_connection:exec(ConnectionRef, ChannelId,
+ "echo testing", infinity),
+
+ ExpectedData = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}},
+ case ssh_test_lib:receive_exec_result(ExpectedData) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
+ Acc;
+ {unexpected_msg,{ssh_cm, ConnectionRef,
+ {exit_status, ChannelId, 0}} = ExitStatus} ->
+ ct:log("0: Collected data ~p", [ExitStatus]),
+ ssh_test_lib:receive_exec_result(ExpectedData, ConnectionRef, ChannelId),
+ Acc;
+ Other ->
+ ct:log("~p failed: ~p",[Kex,Other]),
+ false
+ end
+ end, true, KexAlgos),
+ case Success of
+ true ->
+ ok;
+ false ->
+ {fail, "Kex failed for one or more algos"}
+ end
+ end.
%%--------------------------------------------------------------------
erlang_client_openssh_server_setenv() ->
@@ -380,11 +289,11 @@ erlang_client_openssh_server_setenv(Config) when is_list(Config) ->
{data,0,1, UnxpectedData}}} ->
%% Some os may return things as
%% ENV_TEST: Undefined variable.\n"
- ct:pal("UnxpectedData: ~p", [UnxpectedData]),
+ ct:log("UnxpectedData: ~p", [UnxpectedData]),
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId);
{unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}}
= ExitStatus} ->
- ct:pal("0: Collected data ~p", [ExitStatus]),
+ ct:log("0: Collected data ~p", [ExitStatus]),
ssh_test_lib:receive_exec_result(Data,
ConnectionRef, ChannelId);
Other ->
@@ -426,31 +335,20 @@ erlang_client_openssh_server_publickey_rsa(Config) when is_list(Config) ->
erlang_client_openssh_server_publickey_dsa() ->
[{doc, "Validate using dsa publickey."}].
erlang_client_openssh_server_publickey_dsa(Config) when is_list(Config) ->
- {ok,[[Home]]} = init:get_argument(home),
- KeyFile = filename:join(Home, ".ssh/id_dsa"),
- case file:read_file(KeyFile) of
- {ok, Pem} ->
- case public_key:pem_decode(Pem) of
- [{_,_, not_encrypted}] ->
- ConnectionRef =
- ssh_test_lib:connect(?SSH_DEFAULT_PORT,
- [{public_key_alg, ssh_dsa},
- {user_interaction, false},
- silently_accept_hosts]),
- {ok, Channel} =
- ssh_connection:session_channel(ConnectionRef, infinity),
- ok = ssh_connection:close(ConnectionRef, Channel),
- ok = ssh:close(ConnectionRef);
- _ ->
- {skip, {error, "Has pass phrase can not be used by automated test case"}}
- end;
- _ ->
- {skip, "no ~/.ssh/id_dsa"}
- end.
+ ConnectionRef =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT,
+ [{public_key_alg, ssh_dsa},
+ {user_interaction, false},
+ silently_accept_hosts]),
+ {ok, Channel} =
+ ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh_connection:close(ConnectionRef, Channel),
+ ok = ssh:close(ConnectionRef).
+
%%--------------------------------------------------------------------
-erlang_server_openssh_client_pulic_key_dsa() ->
+erlang_server_openssh_client_public_key_dsa() ->
[{doc, "Validate using dsa publickey."}].
-erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) ->
+erlang_server_openssh_client_public_key_dsa(Config) when is_list(Config) ->
SystemDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
KnownHosts = filename:join(PrivDir, "known_hosts"),
@@ -458,21 +356,25 @@ erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{public_key_alg, ssh_dsa},
{failfun, fun ssh_test_lib:failfun/2}]),
-
+
ct:sleep(500),
Cmd = "ssh -p " ++ integer_to_list(Port) ++
" -o UserKnownHostsFile=" ++ KnownHosts ++
" " ++ Host ++ " 1+1.",
- SshPort = open_port({spawn, Cmd}, [binary]),
+ SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
receive
- {SshPort,{data, <<"2\n">>}} ->
+ {SshPort,{data, <<"2\n">>}} ->
ok
after ?TIMEOUT ->
- ct:fail("Did not receive answer")
+ receive
+ X -> ct:fail("Received: ~p",[X])
+ after 0 ->
+ ct:fail("Did not receive answer")
+ end
end,
- ssh:stop_daemon(Pid).
+ ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
erlang_client_openssh_server_password() ->
@@ -482,12 +384,12 @@ erlang_client_openssh_server_password(Config) when is_list(Config) ->
UserDir = ?config(data_dir, Config),
{error, Reason0} =
ssh:connect(any, ?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user, "foo"},
- {password, "morot"},
- {user_interaction, false},
- {user_dir, UserDir}]),
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
- ct:pal("Test of user foo that does not exist. "
+ ct:log("Test of user foo that does not exist. "
"Error msg: ~p~n", [Reason0]),
User = string:strip(os:cmd("whoami"), right, $\n),
@@ -501,10 +403,10 @@ erlang_client_openssh_server_password(Config) when is_list(Config) ->
{password, "foo"},
{user_interaction, false},
{user_dir, UserDir}]),
- ct:pal("Test of wrong Pasword. "
+ ct:log("Test of wrong Pasword. "
"Error msg: ~p~n", [Reason1]);
_ ->
- ct:pal("Whoami failed reason: ~n", [])
+ ct:log("Whoami failed reason: ~n", [])
end.
%%--------------------------------------------------------------------
@@ -532,21 +434,23 @@ erlang_client_openssh_server_nonexistent_subsystem(Config) when is_list(Config)
receive_hej() ->
receive
<<"Hej", _binary>> = Hej ->
- ct:pal("Expected result: ~p~n", [Hej]);
+ ct:log("Expected result: ~p~n", [Hej]);
<<"Hej\n", _binary>> = Hej ->
- ct:pal("Expected result: ~p~n", [Hej]);
+ ct:log("Expected result: ~p~n", [Hej]);
<<"Hej\r\n", _/binary>> = Hej ->
- ct:pal("Expected result: ~p~n", [Hej]);
+ ct:log("Expected result: ~p~n", [Hej]);
Info ->
Lines = binary:split(Info, [<<"\r\n">>], [global]),
case lists:member(<<"Hej">>, Lines) of
true ->
- ct:pal("Expected result found in lines: ~p~n", [Lines]),
+ ct:log("Expected result found in lines: ~p~n", [Lines]),
ok;
false ->
- ct:pal("Extra info: ~p~n", [Info]),
+ ct:log("Extra info: ~p~n", [Info]),
receive_hej()
end
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
receive_logout() ->
@@ -556,11 +460,15 @@ receive_logout() ->
receive
<<"Connection closed">> ->
ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end;
Info ->
- ct:pal("Extra info when logging out: ~p~n", [Info]),
+ ct:log("Extra info when logging out: ~p~n", [Info]),
receive_logout()
- end.
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end.
receive_normal_exit(Shell) ->
receive
@@ -570,6 +478,8 @@ receive_normal_exit(Shell) ->
receive_normal_exit(Shell);
Other ->
ct:fail({unexpected_msg, Other})
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
extra_logout() ->
@@ -581,26 +491,17 @@ extra_logout() ->
end.
%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
%% Check if we have a "newer" ssh client that supports these test cases
-%%--------------------------------------------------------------------
check_ssh_client_support(Config) ->
- Port = open_port({spawn, "ssh -Q cipher"}, [exit_status, stderr_to_stdout]),
- case check_ssh_client_support2(Port) of
- 0 -> % exit status from command (0 == ok)
+ case ssh_test_lib:ssh_client_supports_Q() of
+ true ->
ssh:start(),
Config;
_ ->
{skip, "test case not supported by ssh client"}
end.
-check_ssh_client_support2(P) ->
- receive
- {P, {data, _A}} ->
- check_ssh_client_support2(P);
- {P, {exit_status, E}} ->
- E
- after 5000 ->
- ct:pal("Openssh command timed out ~n"),
- -1
- end.
+comment(AtomList) ->
+ ct:comment(
+ string:join(lists:map(fun erlang:atom_to_list/1, AtomList),
+ ", ")).