%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2005-2013. 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/. %% %% 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. %% %% %CopyrightEnd% %% %% -module(ftp_suite_lib). -include_lib("test_server/include/test_server.hrl"). -include_lib("test_server/include/test_server_line.hrl"). -include("inets_test_lib.hrl"). %% Test server specific exports % -export([init_per_testcase/2, end_per_testcase/2]). -compile(export_all). -record(progress, { current = 0, total }). -define(FTP_USER, "anonymous"). -define(FTP_PASS, passwd()). -define(FTP_PORT, 21). -define(BAD_HOST, "badhostname"). -define(BAD_USER, "baduser"). -define(BAD_DIR, "baddirectory"). -ifdef(ftp_debug_client). -define(ftp_open(Host, Flags), do_ftp_open(Host, [{debug, debug}, {timeout, timer:seconds(15)} | Flags])). -else. -ifdef(ftp_trace_client). -define(ftp_open(Host, Flags), do_ftp_open(Host, [{debug, trace}, {timeout, timer:seconds(15)} | Flags])). -else. -define(ftp_open(Host, Flags), do_ftp_open(Host, [{verbose, true}, {timeout, timer:seconds(15)} | Flags])). -endif. -endif. %% -- Tickets -- tickets(doc) -> "Test cases for reported bugs"; tickets(suite) -> [ticket_6035]. %% -- ftpd_init(FtpdTag, Config) -> %% Get the host name(s) of FTP server Hosts = case ct:get_config(ftpd_hosts) of undefined -> ftpd_hosts(data_dir(Config)); H -> H end, p("ftpd_init -> " "~n Hosts: ~p" "~n Config: ~p" "~n FtpdTag: ~p", [Hosts, Config, FtpdTag]), %% Get the first host that actually have a running FTP server case lists:keysearch(FtpdTag, 1, Hosts) of {value, {_, TagHosts}} when is_list(TagHosts) -> inets:start(), case (catch get_ftpd_host(TagHosts)) of {ok, Host} -> inets:stop(), [{ftp_remote_host, Host}|Config]; _ -> inets:stop(), Reason = lists:flatten( io_lib:format("Could not find a valid " "FTP server for ~p (~p)", [FtpdTag, TagHosts])), {skip, Reason} end; _ -> Reason = lists:flatten( io_lib:format("No host(s) running FTPD server " "for ~p", [FtpdTag])), {skip, Reason} end. ftpd_fin(Config) -> lists:keydelete(ftp_remote_host, 1, Config). get_ftpd_host([]) -> {error, no_host}; get_ftpd_host([Host|Hosts]) -> p("get_ftpd_host -> entry with" "~n Host: ~p" "~n", [Host]), case (catch ftp:open(Host, [{port, ?FTP_PORT}, {timeout, 20000}])) of {ok, Pid} -> (catch ftp:close(Pid)), {ok, Host}; _ -> get_ftpd_host(Hosts) end. %%-------------------------------------------------------------------- dirty_select_ftpd_host(Config) -> Hosts = case ct:get_config(ftpd_hosts) of undefined -> ftpd_hosts(data_dir(Config)); H -> H end, dirty_select_ftpd_host2(Hosts). dirty_select_ftpd_host2([]) -> throw({error, not_found}); dirty_select_ftpd_host2([{PlatformTag, Hosts} | PlatformHosts]) -> case dirty_select_ftpd_host3(Hosts) of none -> dirty_select_ftpd_host2(PlatformHosts); {ok, Host} -> {PlatformTag, Host} end. dirty_select_ftpd_host3([]) -> none; dirty_select_ftpd_host3([Host|Hosts]) when is_list(Host) -> case dirty_select_ftpd_host4(Host) of true -> {ok, Host}; false -> dirty_select_ftpd_host3(Hosts) end; dirty_select_ftpd_host3([_|Hosts]) -> dirty_select_ftpd_host3(Hosts). %% This is a very simple and dirty test that there is a %% (FTP) deamon on the other end. dirty_select_ftpd_host4(Host) -> Port = 21, IpFam = inet, Opts = [IpFam, binary, {packet, 0}, {active, false}], Timeout = ?SECS(5), case gen_tcp:connect(Host, Port, Opts, Timeout) of {ok, Sock} -> gen_tcp:close(Sock), true; _Error -> false end. %%-------------------------------------------------------------------- test_filenames() -> {ok, Host} = inet:gethostname(), File = Host ++ "_ftp_test.txt", NewFile = "new_" ++ File, {File, NewFile}. %%-------------------------------------------------------------------- %% Function: init_per_testcase(Case, Config) -> Config %% Case - atom() %% Name of the test case that is about to be run. %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. %% %% Description: Initiation before each test case %% %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_testcase(Case, Config) when (Case =:= open) orelse (Case =:= open_port) -> put(ftp_testcase, Case), io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), inets:start(), NewConfig = data_dir(Config), watch_dog(NewConfig); init_per_testcase(Case, Config) -> put(ftp_testcase, Case), do_init_per_testcase(Case, Config). do_init_per_testcase(Case, Config) when (Case =:= passive_user) -> io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE,Case]), inets:start(), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), case (catch ?ftp_open(Host, [{mode, passive}])) of {ok, Pid} -> [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> SKIP end; do_init_per_testcase(Case, Config) when (Case =:= active_user) -> io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), inets:start(), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), case (catch ?ftp_open(Host, [{mode, active}])) of {ok, Pid} -> [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> SKIP end; do_init_per_testcase(Case, Config) when (Case =:= progress_report_send) orelse (Case =:= progress_report_recv) -> inets:start(), io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), Opts = [{port, ?FTP_PORT}, {verbose, true}, {progress, {?MODULE, progress, #progress{}}}], case ftp:open(Host, Opts) of {ok, Pid} -> ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS), [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> SKIP end; do_init_per_testcase(Case, Config) -> io:format(user,"~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), inets:start(), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), Opts1 = if ((Case =:= passive_ip_v6_disabled) orelse (Case =:= active_ip_v6_disabled)) -> [{ipfamily, inet}]; true -> [] end, Opts2 = case string:tokens(atom_to_list(Case), [$_]) of ["active" | _] -> [{mode, active} | Opts1]; _ -> [{mode, passive} | Opts1] end, case (catch ?ftp_open(Host, Opts2)) of {ok, Pid} -> ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS), [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> SKIP end. %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() %% Name of the test case that is about to be run. %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(_, Config) -> NewConfig = close_connection(Config), Dog = ?config(watchdog, NewConfig), inets:stop(), test_server:timetrap_cancel(Dog), ok. %%------------------------------------------------------------------------- %% Suites similar for all hosts. %%------------------------------------------------------------------------- passive(suite) -> [ passive_user, passive_pwd, passive_cd, passive_lcd, passive_ls, passive_nlist, passive_rename, passive_delete, passive_mkdir, passive_send, passive_send_bin, passive_send_chunk, passive_append, passive_append_bin, passive_append_chunk, passive_recv, passive_recv_bin, passive_recv_chunk, passive_type, passive_quote, passive_ip_v6_disabled ]. active(suite) -> [ active_user, active_pwd, active_cd, active_lcd, active_ls, active_nlist, active_rename, active_delete, active_mkdir, active_send, active_send_bin, active_send_chunk, active_append, active_append_bin, active_append_chunk, active_recv, active_recv_bin, active_recv_chunk, active_type, active_quote, active_ip_v6_disabled ]. %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- open(doc) -> ["Open an ftp connection to a host and close the connection." "Also check that !-messages does not disturbe the connection"]; open(suite) -> []; open(Config) when is_list(Config) -> Host = ftp_host(Config), (catch tc_open(Host)). tc_open(Host) -> p("tc_open -> entry with" "~n Host: ~p", [Host]), {ok, Pid} = ?ftp_open(Host, []), ok = ftp:close(Pid), p("tc_open -> try (ok) open 1"), {ok, Pid1} = ftp:open({option_list, [{host,Host}, {port, ?FTP_PORT}, {flags, [verbose]}, {timeout, 30000}]}), ok = ftp:close(Pid1), p("tc_open -> try (fail) open 2"), {error, ehost} = ftp:open({option_list, [{port, ?FTP_PORT}, {flags, [verbose]}]}), {ok, Pid2} = ftp:open(Host), ok = ftp:close(Pid2), p("tc_open -> try (ok) open 3"), {ok, NewHost} = inet:getaddr(Host, inet), {ok, Pid3} = ftp:open(NewHost), ftp:user(Pid3, ?FTP_USER, ?FTP_PASS), Pid3 ! foobar, test_server:sleep(5000), {message_queue_len, 0} = process_info(self(), message_queue_len), ["200" ++ _] = ftp:quote(Pid3, "NOOP"), ok = ftp:close(Pid3), %% Bad input that has default values are ignored and the defult %% is used. p("tc_open -> try (ok) open 4"), {ok, Pid4} = ftp:open({option_list, [{host, Host}, {port, badarg}, {flags, [verbose]}, {timeout, 30000}]}), test_server:sleep(100), ok = ftp:close(Pid4), p("tc_open -> try (ok) open 5"), {ok, Pid5} = ftp:open({option_list, [{host, Host}, {port, ?FTP_PORT}, {flags, [verbose]}, {timeout, -42}]}), test_server:sleep(100), ok = ftp:close(Pid5), p("tc_open -> try (ok) open 6"), {ok, Pid6} = ftp:open({option_list, [{host, Host}, {port, ?FTP_PORT}, {flags, [verbose]}, {mode, cool}]}), test_server:sleep(100), ok = ftp:close(Pid6), p("tc_open -> try (ok) open 7"), {ok, Pid7} = ftp:open(Host, [{port, ?FTP_PORT}, {verbose, true}, {timeout, 30000}]), ok = ftp:close(Pid7), p("tc_open -> try (ok) open 8"), {ok, Pid8} = ftp:open(Host, ?FTP_PORT), ok = ftp:close(Pid8), p("tc_open -> try (ok) open 9"), {ok, Pid9} = ftp:open(Host, [{port, ?FTP_PORT}, {verbose, true}, {timeout, 30000}, {dtimeout, -99}]), ok = ftp:close(Pid9), p("tc_open -> try (ok) open 10"), {ok, Pid10} = ftp:open(Host, [{port, ?FTP_PORT}, {verbose, true}, {timeout, 30000}, {dtimeout, "foobar"}]), ok = ftp:close(Pid10), p("tc_open -> try (ok) open 11"), {ok, Pid11} = ftp:open(Host, [{port, ?FTP_PORT}, {verbose, true}, {timeout, 30000}, {dtimeout, 1}]), ok = ftp:close(Pid11), p("tc_open -> done"), ok. %%------------------------------------------------------------------------- open_port(doc) -> ["Open an ftp connection to a host with given port number " "and close the connection."]; % See also OTP-3892 open_port(suite) -> []; open_port(Config) when is_list(Config) -> Host = ftp_host(Config), {ok, Pid} = ftp:open(Host, [{port, ?FTP_PORT}]), ok = ftp:close(Pid), {error, ehost} = ftp:open(?BAD_HOST, []), ok. %%------------------------------------------------------------------------- passive_user(doc) -> ["Open an ftp connection to a host, and logon as anonymous ftp."]; passive_user(suite) -> []; passive_user(Config) when is_list(Config) -> Pid = ?config(ftp, Config), p("Pid: ~p",[Pid]), do_user(Pid). %%------------------------------------------------------------------------- passive_pwd(doc) -> ["Test ftp:pwd/1 & ftp:lpwd/1"]; passive_pwd(suite) -> []; passive_pwd(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_pwd(Pid). %%------------------------------------------------------------------------- passive_cd(doc) -> ["Open an ftp connection, log on as anonymous ftp, and cd to the" "directory \"/pub\" and the to the non-existent directory."]; passive_cd(suite) -> []; passive_cd(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_cd(Pid). %%------------------------------------------------------------------------- passive_lcd(doc) -> ["Test api function ftp:lcd/2"]; passive_lcd(suite) -> []; passive_lcd(Config) when is_list(Config) -> Pid = ?config(ftp, Config), PrivDir = ?config(priv_dir, Config), do_lcd(Pid, PrivDir). %%------------------------------------------------------------------------- passive_ls(doc) -> ["Open an ftp connection; ls the current directory, and the " "\"incoming\" directory. We assume that ls never fails, since " "it's output is meant to be read by humans. "]; passive_ls(suite) -> []; passive_ls(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_ls(Pid). %%------------------------------------------------------------------------- passive_nlist(doc) -> ["Open an ftp connection; nlist the current directory, and the " "\"incoming\" directory. Nlist does not behave consistenly over " "operating systems. On some it is an error to have an empty " "directory."]; passive_nlist(suite) -> []; passive_nlist(Config) when is_list(Config) -> Pid = ?config(ftp, Config), WildcardSupport = ?config(wildcard_support, Config), do_nlist(Pid, WildcardSupport). %%------------------------------------------------------------------------- passive_rename(doc) -> ["Transfer a file to the server, and rename it; then remove it."]; passive_rename(suite) -> []; passive_rename(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_rename(Pid, Config). %%------------------------------------------------------------------------- passive_delete(doc) -> ["Transfer a file to the server, and then delete it"]; passive_delete(suite) -> []; passive_delete(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_delete(Pid, Config). %%------------------------------------------------------------------------- passive_mkdir(doc) -> ["Make a remote directory, cd to it, go to parent directory, and " "remove the directory."]; passive_mkdir(suite) -> []; passive_mkdir(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_mkdir(Pid). %%------------------------------------------------------------------------- passive_send(doc) -> ["Create a local file in priv_dir; open an ftp connection to a host; " "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to " "priv_dir; send the file; get a directory listing and check that " "the file is on the list;, delete the remote file; get another listing " "and check that the file is not on the list; close the session; " "delete the local file."]; passive_send(suite) -> []; passive_send(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send(Pid, Config). %%------------------------------------------------------------------------- passive_append(doc) -> ["Create a local file in priv_dir; open an ftp connection to a host; " "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to " "priv_dir; append the file to a file at the remote side that not exits" "this will create the file at the remote side. Then it append the file " "again. When this is done it recive the remote file and control that" "the content is doubled in it.After that it will remove the files"]; passive_append(suite) -> []; passive_append(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_append(Pid, Config). %%------------------------------------------------------------------------- passive_send_bin(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "send a binary; remove file; close the connection."]; passive_send_bin(suite) -> []; passive_send_bin(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send_bin(Pid, Config). %%------------------------------------------------------------------------- passive_append_bin(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "append a binary twice; get the file and compare the content" "remove file; close the connection."]; passive_append_bin(suite) -> []; passive_append_bin(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_append_bin(Pid, Config). %%------------------------------------------------------------------------- passive_send_chunk(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "send chunks; remove file; close the connection."]; passive_send_chunk(suite) -> []; passive_send_chunk(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send_chunk(Pid, Config). %%------------------------------------------------------------------------- passive_append_chunk(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "append chunks;control content remove file; close the connection."]; passive_append_chunk(suite) -> []; passive_append_chunk(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_append_chunk(Pid, Config). %%------------------------------------------------------------------------- passive_recv(doc) -> ["Create a local file and transfer it to the remote host into the " "the \"incoming\" directory, remove " "the local file. Then open a new connection; cd to \"incoming\", " "lcd to the private directory; receive the file; delete the " "remote file; close connection; check that received file is in " "the correct directory; cleanup." ]; passive_recv(suite) -> []; passive_recv(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_recv(Pid, Config). %%------------------------------------------------------------------------- passive_recv_bin(doc) -> ["Send a binary to the remote host; and retreive " "the file; then remove the file."]; passive_recv_bin(suite) -> []; passive_recv_bin(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_recv_bin(Pid, Config). %%------------------------------------------------------------------------- passive_recv_chunk(doc) -> ["Send a binary to the remote host; Connect again, and retreive " "the file; then remove the file."]; passive_recv_chunk(suite) -> []; passive_recv_chunk(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_recv_chunk(Pid, Config). %%------------------------------------------------------------------------- passive_type(doc) -> ["Test that we can change btween ASCCI and binary transfer mode"]; passive_type(suite) -> []; passive_type(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_type(Pid). %%------------------------------------------------------------------------- passive_quote(doc) -> [""]; passive_quote(suite) -> []; passive_quote(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_quote(Pid). %%------------------------------------------------------------------------- passive_ip_v6_disabled(doc) -> ["Test ipv4 command PASV"]; passive_ip_v6_disabled(suite) -> []; passive_ip_v6_disabled(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send(Pid, Config). %%------------------------------------------------------------------------- active_user(doc) -> ["Open an ftp connection to a host, and logon as anonymous ftp."]; active_user(suite) -> []; active_user(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_user(Pid). %%------------------------------------------------------------------------- active_pwd(doc) -> ["Test ftp:pwd/1 & ftp:lpwd/1"]; active_pwd(suite) -> []; active_pwd(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_pwd(Pid). %%------------------------------------------------------------------------- active_cd(doc) -> ["Open an ftp connection, log on as anonymous ftp, and cd to the" "directory \"/pub\" and to a non-existent directory."]; active_cd(suite) -> []; active_cd(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_cd(Pid). %%------------------------------------------------------------------------- active_lcd(doc) -> ["Test api function ftp:lcd/2"]; active_lcd(suite) -> []; active_lcd(Config) when is_list(Config) -> Pid = ?config(ftp, Config), PrivDir = ?config(priv_dir, Config), do_lcd(Pid, PrivDir). %%------------------------------------------------------------------------- active_ls(doc) -> ["Open an ftp connection; ls the current directory, and the " "\"incoming\" directory. We assume that ls never fails, since " "it's output is meant to be read by humans. "]; active_ls(suite) -> []; active_ls(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_ls(Pid). %%------------------------------------------------------------------------- active_nlist(doc) -> ["Open an ftp connection; nlist the current directory, and the " "\"incoming\" directory. Nlist does not behave consistenly over " "operating systems. On some it is an error to have an empty " "directory."]; active_nlist(suite) -> []; active_nlist(Config) when is_list(Config) -> Pid = ?config(ftp, Config), WildcardSupport = ?config(wildcard_support, Config), do_nlist(Pid, WildcardSupport). %%------------------------------------------------------------------------- active_rename(doc) -> ["Transfer a file to the server, and rename it; then remove it."]; active_rename(suite) -> []; active_rename(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_rename(Pid, Config). %%------------------------------------------------------------------------- active_delete(doc) -> ["Transfer a file to the server, and then delete it"]; active_delete(suite) -> []; active_delete(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_delete(Pid, Config). %%------------------------------------------------------------------------- active_mkdir(doc) -> ["Make a remote directory, cd to it, go to parent directory, and " "remove the directory."]; active_mkdir(suite) -> []; active_mkdir(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_mkdir(Pid). %%------------------------------------------------------------------------- active_send(doc) -> ["Create a local file in priv_dir; open an ftp connection to a host; " "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to " "priv_dir; send the file; get a directory listing and check that " "the file is on the list;, delete the remote file; get another listing " "and check that the file is not on the list; close the session; " "delete the local file."]; active_send(suite) -> []; active_send(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send(Pid, Config). %%------------------------------------------------------------------------- active_append(doc) -> ["Create a local file in priv_dir; open an ftp connection to a host; " "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to " "priv_dir; append the file to a file at the remote side that not exits" "this will create the file at the remote side. Then it append the file " "again. When this is done it recive the remote file and control that" "the content is doubled in it.After that it will remove the files"]; active_append(suite) -> []; active_append(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_append(Pid, Config). %%------------------------------------------------------------------------- active_send_bin(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "send a binary; remove file; close the connection."]; active_send_bin(suite) -> []; active_send_bin(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send_bin(Pid, Config). %%------------------------------------------------------------------------- active_append_bin(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "append a binary twice; get the file and compare the content" "remove file; close the connection."]; active_append_bin(suite) -> []; active_append_bin(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_append_bin(Pid, Config). %%------------------------------------------------------------------------- active_send_chunk(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "send chunks; remove file; close the connection."]; active_send_chunk(suite) -> []; active_send_chunk(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send_chunk(Pid, Config). %%------------------------------------------------------------------------- active_append_chunk(doc) -> ["Open a connection to a host; cd to the directory \"incoming\"; " "append chunks;control content remove file; close the connection."]; active_append_chunk(suite) -> []; active_append_chunk(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_append_chunk(Pid, Config). %%------------------------------------------------------------------------- active_recv(doc) -> ["Create a local file and transfer it to the remote host into the " "the \"incoming\" directory, remove " "the local file. Then open a new connection; cd to \"incoming\", " "lcd to the private directory; receive the file; delete the " "remote file; close connection; check that received file is in " "the correct directory; cleanup." ]; active_recv(suite) -> []; active_recv(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_recv(Pid, Config). %%------------------------------------------------------------------------- active_recv_bin(doc) -> ["Send a binary to the remote host; and retreive " "the file; then remove the file."]; active_recv_bin(suite) -> []; active_recv_bin(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_recv_bin(Pid, Config). %%------------------------------------------------------------------------- active_recv_chunk(doc) -> ["Send a binary to the remote host; Connect again, and retreive " "the file; then remove the file."]; active_recv_chunk(suite) -> []; active_recv_chunk(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_recv_chunk(Pid, Config). %%------------------------------------------------------------------------- active_type(doc) -> ["Test that we can change btween ASCCI and binary transfer mode"]; active_type(suite) -> []; active_type(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_type(Pid). %%------------------------------------------------------------------------- active_quote(doc) -> [""]; active_quote(suite) -> []; active_quote(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_quote(Pid). %%------------------------------------------------------------------------- active_ip_v6_disabled(doc) -> ["Test ipv4 command PORT"]; active_ip_v6_disabled(suite) -> []; active_ip_v6_disabled(Config) when is_list(Config) -> Pid = ?config(ftp, Config), do_send(Pid, Config). %%------------------------------------------------------------------------- api_missuse(doc)-> ["Test that behaviour of the ftp process if the api is abused"]; api_missuse(suite) -> []; api_missuse(Config) when is_list(Config) -> p("api_missuse -> entry"), Flag = process_flag(trap_exit, true), Pid = ?config(ftp, Config), Host = ftp_host(Config), %% Serious programming fault, connetion will be shut down p("api_missuse -> verify bad call termination (~p)", [Pid]), case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of {error, {connection_terminated, 'API_violation'}} -> ok; Unexpected1 -> exit({unexpected_result, Unexpected1}) end, test_server:sleep(500), undefined = process_info(Pid, status), p("api_missuse -> start new client"), {ok, Pid2} = ?ftp_open(Host, []), %% Serious programming fault, connetion will be shut down p("api_missuse -> verify bad cast termination"), gen_server:cast(Pid2, {self(), foobar, 10}), test_server:sleep(500), undefined = process_info(Pid2, status), p("api_missuse -> start new client"), {ok, Pid3} = ?ftp_open(Host, []), %% Could be an innocent misstake the connection lives. p("api_missuse -> verify bad bang"), Pid3 ! foobar, test_server:sleep(500), {status, _} = process_info(Pid3, status), process_flag(trap_exit, Flag), p("api_missuse -> done"), ok. %%------------------------------------------------------------------------- not_owner(doc) -> ["Test what happens if a process that not owns the connection tries " "to use it"]; not_owner(suite) -> []; not_owner(Config) when is_list(Config) -> Pid = ?config(ftp, Config), OtherPid = spawn_link(?MODULE, not_owner, [Pid, self()]), receive {OtherPid, ok} -> {ok, _} = ftp:pwd(Pid) end, ok. not_owner(FtpPid, Pid) -> {error, not_connection_owner} = ftp:pwd(FtpPid), ftp:close(FtpPid), test_server:sleep(100), Pid ! {self(), ok}. %%------------------------------------------------------------------------- progress_report(doc) -> ["Solaris 8 sparc test the option progress."]; progress_report(suite) -> [progress_report_send, progress_report_recv]. %% -- progress_report_send(doc) -> ["Test the option progress for ftp:send/[2,3]"]; progress_report_send(suite) -> []; progress_report_send(Config) when is_list(Config) -> Pid = ?config(ftp, Config), ReportPid = spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]), do_send(Pid, Config), receive {ReportPid, ok} -> ok end. %% -- progress_report_recv(doc) -> ["Test the option progress for ftp:recv/[2,3]"]; progress_report_recv(suite) -> []; progress_report_recv(Config) when is_list(Config) -> Pid = ?config(ftp, Config), ReportPid = spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]), do_recv(Pid, Config), receive {ReportPid, ok} -> ok end, ok. progress(#progress{} = Progress , _File, {file_size, Total}) -> progress_report_receiver ! start, Progress#progress{total = Total}; progress(#progress{total = Total, current = Current} = Progress, _File, {transfer_size, 0}) -> progress_report_receiver ! finish, case Total of unknown -> ok; Current -> ok; _ -> test_server:fail({error, {progress, {total, Total}, {current, Current}}}) end, Progress; progress(#progress{current = Current} = Progress, _File, {transfer_size, Size}) -> progress_report_receiver ! update, Progress#progress{current = Current + Size}. progress_report_receiver_init(Pid, N) -> register(progress_report_receiver, self()), receive start -> ok end, progress_report_receiver_loop(Pid, N-1). progress_report_receiver_loop(Pid, N) -> receive update -> progress_report_receiver_loop(Pid, N); finish when N =:= 0 -> Pid ! {self(), ok}; finish -> Pid ! {self(), ok}, receive start -> ok end, progress_report_receiver_loop(Pid, N-1) end. %%------------------------------------------------------------------------- %% Ticket test cases %%------------------------------------------------------------------------- ticket_6035(doc) -> ["Test that owning process that exits with reason " "'shutdown' does not cause an error message."]; ticket_6035(suite) -> []; ticket_6035(Config) -> p("ticket_6035 -> entry with" "~n Config: ~p", [Config]), PrivDir = ?config(priv_dir, Config), LogFile = filename:join([PrivDir,"ticket_6035.log"]), try begin p("ticket_6035 -> select ftpd host"), Host = dirty_select_ftpd_host(Config), p("ticket_6035 -> ftpd host selected (~p) => now spawn ftp owner", [Host]), Pid = spawn(?MODULE, open_wait_6035, [Host, self()]), p("ticket_6035 -> waiter spawned: ~p => now open error logfile (~p)", [Pid, LogFile]), error_logger:logfile({open, LogFile}), p("ticket_6035 -> error logfile open => now kill waiter process"), true = kill_ftp_proc_6035(Pid, LogFile), p("ticket_6035 -> waiter process killed => now close error logfile"), error_logger:logfile(close), p("ticket_6035 -> done", []), ok end catch throw:{error, not_found} -> {skip, "No available FTP servers"} end. kill_ftp_proc_6035(Pid, LogFile) -> p("kill_ftp_proc_6035 -> entry"), receive open -> p("kill_ftp_proc_6035 -> received open => now issue shutdown"), exit(Pid, shutdown), kill_ftp_proc_6035(Pid, LogFile); {open_failed, Reason} -> p("kill_ftp_proc_6035 -> received open_failed" "~n Reason: ~p", [Reason]), exit({skip, {failed_openening_server_connection, Reason}}) after 5000 -> p("kill_ftp_proc_6035 -> timeout"), is_error_report_6035(LogFile) end. open_wait_6035({Tag, FtpServer}, From) -> p("open_wait_6035 -> try connect to [~p] ~s for ~p", [Tag, FtpServer, From]), case ftp:open(FtpServer, [{timeout, timer:seconds(15)}]) of {ok, Pid} -> p("open_wait_6035 -> connected (~p), now login", [Pid]), LoginResult = ftp:user(Pid,"anonymous","kldjf"), p("open_wait_6035 -> login result: ~p", [LoginResult]), From ! open, receive dummy -> p("open_wait_6035 -> received dummy"), ok after 10000 -> p("open_wait_6035 -> timeout"), ok end, p("open_wait_6035 -> done(ok)"), ok; {error, Reason} -> p("open_wait_6035 -> open failed" "~n Reason: ~p", [Reason]), From ! {open_failed, {Reason, FtpServer}}, p("open_wait_6035 -> done(error)"), ok end. is_error_report_6035(LogFile) -> p("is_error_report_6035 -> entry"), Res = case file:read_file(LogFile) of {ok, Bin} -> Txt = binary_to_list(Bin), p("is_error_report_6035 -> logfile read: ~n~p", [Txt]), read_log_6035(Txt); _ -> false end, p("is_error_report_6035 -> logfile read result: " "~n ~p", [Res]), %% file:delete(LogFile), Res. read_log_6035("=ERROR REPORT===="++_Rest) -> p("read_log_6035 -> ERROR REPORT detected"), true; read_log_6035([H|T]) -> p("read_log_6035 -> OTHER: " "~p", [H]), read_log_6035(T); read_log_6035([]) -> p("read_log_6035 -> done"), false. %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- do_user(Pid) -> {error, euser} = ftp:user(Pid, ?BAD_USER, ?FTP_PASS), ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS), ok. do_pwd(Pid) -> {ok, "/"} = ftp:pwd(Pid), {ok, Path} = ftp:lpwd(Pid), {ok, Path} = file:get_cwd(), ok. do_cd(Pid) -> ok = ftp:cd(Pid, "/pub"), {error, epath} = ftp:cd(Pid, ?BAD_DIR), ok. do_lcd(Pid, Dir) -> ok = ftp:lcd(Pid, Dir), {error, epath} = ftp:lcd(Pid, ?BAD_DIR), ok. do_ls(Pid) -> {ok, _} = ftp:ls(Pid), {ok, _} = ftp:ls(Pid, "incoming"), %% neither nlist nor ls operates on a directory %% they operate on a pathname, which *can* be a %% directory, but can also be a filename or a group %% of files (including wildcards). {ok, _} = ftp:ls(Pid, "incom*"), ok. do_nlist(Pid, WildcardSupport) -> {ok, _} = ftp:nlist(Pid), {ok, _} = ftp:nlist(Pid, "incoming"), %% neither nlist nor ls operates on a directory %% they operate on a pathname, which *can* be a %% directory, but can also be a filename or a group %% of files (including wildcards). case WildcardSupport of true -> {ok, _} = ftp:nlist(Pid, "incom*"), ok; _ -> ok end. do_rename(Pid, Config) -> PrivDir = ?config(priv_dir, Config), LFile = ?config(file, Config), NewLFile = ?config(new_file, Config), AbsLFile = filename:absname(LFile, PrivDir), Contents = "ftp_SUITE test ...", ok = file:write_file(AbsLFile, list_to_binary(Contents)), ok = ftp:cd(Pid, "incoming"), ok = ftp:lcd(Pid, PrivDir), ftp:delete(Pid, LFile), % reset ftp:delete(Pid, NewLFile), % reset ok = ftp:send(Pid, LFile), {error, epath} = ftp:rename(Pid, NewLFile, LFile), ok = ftp:rename(Pid, LFile, NewLFile), ftp:delete(Pid, LFile), % cleanup ftp:delete(Pid, NewLFile), % cleanup ok. do_delete(Pid, Config) -> PrivDir = ?config(priv_dir, Config), LFile = ?config(file, Config), AbsLFile = filename:absname(LFile, PrivDir), Contents = "ftp_SUITE test ...", ok = file:write_file(AbsLFile, list_to_binary(Contents)), ok = ftp:cd(Pid, "incoming"), ok = ftp:lcd(Pid, PrivDir), ftp:delete(Pid,LFile), % reset ok = ftp:send(Pid, LFile), ok = ftp:delete(Pid,LFile), ok. do_mkdir(Pid) -> {A, B, C} = erlang:now(), NewDir = "nisse_" ++ integer_to_list(A) ++ "_" ++ integer_to_list(B) ++ "_" ++ integer_to_list(C), ok = ftp:cd(Pid, "incoming"), {ok, CurrDir} = ftp:pwd(Pid), ok = ftp:mkdir(Pid, NewDir), ok = ftp:cd(Pid, NewDir), ok = ftp:cd(Pid, CurrDir), ok = ftp:rmdir(Pid, NewDir), ok. do_send(Pid, Config) -> PrivDir = ?config(priv_dir, Config), LFile = ?config(file, Config), RFile = LFile ++ ".remote", AbsLFile = filename:absname(LFile, PrivDir), Contents = "ftp_SUITE test ...", ok = file:write_file(AbsLFile, list_to_binary(Contents)), ok = ftp:cd(Pid, "incoming"), ok = ftp:lcd(Pid, PrivDir), ok = ftp:send(Pid, LFile, RFile), {ok, RFilesString} = ftp:nlist(Pid), RFiles = split(RFilesString), true = lists:member(RFile, RFiles), ok = ftp:delete(Pid, RFile), case ftp:nlist(Pid) of {error, epath} -> ok; % No files {ok, RFilesString1} -> RFiles1 = split(RFilesString1), false = lists:member(RFile, RFiles1) end, ok = file:delete(AbsLFile). do_append(Pid, Config) -> PrivDir = ?config(priv_dir, Config), LFile = ?config(file, Config), RFile = ?config(new_file, Config), AbsLFile = filename:absname(LFile, PrivDir), Contents = "ftp_SUITE test:appending\r\n", ok = file:write_file(AbsLFile, list_to_binary(Contents)), ok = ftp:cd(Pid, "incoming"), ok = ftp:lcd(Pid, PrivDir), %% remove files from earlier failed test case ftp:delete(Pid, RFile), ftp:delete(Pid, LFile), ok = ftp:append(Pid, LFile, RFile), ok = ftp:append(Pid, LFile, RFile), ok = ftp:append(Pid, LFile), %% Control the contents of the file {ok, Bin1} = ftp:recv_bin(Pid, RFile), ok = ftp:delete(Pid, RFile), ok = file:delete(AbsLFile), ok = check_content(binary_to_list(Bin1), Contents, double), {ok, Bin2} = ftp:recv_bin(Pid, LFile), ok = ftp:delete(Pid, LFile), ok = check_content(binary_to_list(Bin2), Contents, singel), ok. do_send_bin(Pid, Config) -> File = ?config(file, Config), Contents = "ftp_SUITE test ...", Bin = list_to_binary(Contents), ok = ftp:cd(Pid, "incoming"), {error, enotbinary} = ftp:send_bin(Pid, Contents, File), ok = ftp:send_bin(Pid, Bin, File), {ok, RFilesString} = ftp:nlist(Pid), RFiles = split(RFilesString), true = lists:member(File, RFiles), ok = ftp:delete(Pid, File), ok. do_append_bin(Pid, Config) -> File = ?config(file, Config), Contents = "ftp_SUITE test ...", Bin = list_to_binary(Contents), ok = ftp:cd(Pid, "incoming"), {error, enotbinary} = ftp:append_bin(Pid, Contents, File), ok = ftp:append_bin(Pid, Bin, File), ok = ftp:append_bin(Pid, Bin, File), %% Control the contents of the file {ok, Bin2} = ftp:recv_bin(Pid, File), ok = ftp:delete(Pid,File), ok = check_content(binary_to_list(Bin2),binary_to_list(Bin), double). do_send_chunk(Pid, Config) -> File = ?config(file, Config), Contents = "ftp_SUITE test ...", Bin = list_to_binary(Contents), ok = ftp:cd(Pid, "incoming"), ok = ftp:send_chunk_start(Pid, File), {error, echunk} = ftp:cd(Pid, "incoming"), {error, enotbinary} = ftp:send_chunk(Pid, Contents), ok = ftp:send_chunk(Pid, Bin), ok = ftp:send_chunk(Pid, Bin), ok = ftp:send_chunk_end(Pid), {ok, RFilesString} = ftp:nlist(Pid), RFiles = split(RFilesString), true = lists:member(File, RFiles), ok = ftp:delete(Pid, File), ok. do_append_chunk(Pid, Config) -> File = ?config(file, Config), Contents = ["ER","LE","RL"], ok = ftp:cd(Pid, "incoming"), ok = ftp:append_chunk_start(Pid, File), {error, enotbinary} = ftp:append_chunk(Pid, lists:nth(1,Contents)), ok = ftp:append_chunk(Pid,list_to_binary(lists:nth(1,Contents))), ok = ftp:append_chunk(Pid,list_to_binary(lists:nth(2,Contents))), ok = ftp:append_chunk(Pid,list_to_binary(lists:nth(3,Contents))), ok = ftp:append_chunk_end(Pid), %%Control the contents of the file {ok, Bin2} = ftp:recv_bin(Pid, File), ok = check_content(binary_to_list(Bin2),"ERL", double), ok = ftp:delete(Pid, File), ok. do_recv(Pid, Config) -> PrivDir = ?config(priv_dir, Config), File = ?config(file, Config), Newfile = ?config(new_file, Config), AbsFile = filename:absname(File, PrivDir), Contents = "ftp_SUITE:recv test ...", ok = file:write_file(AbsFile, list_to_binary(Contents)), ok = ftp:cd(Pid, "incoming"), ftp:delete(Pid, File), % reset ftp:lcd(Pid, PrivDir), ok = ftp:send(Pid, File), ok = file:delete(AbsFile), % cleanup test_server:sleep(100), ok = ftp:lcd(Pid, PrivDir), ok = ftp:recv(Pid, File), {ok, Files} = file:list_dir(PrivDir), true = lists:member(File, Files), ok = file:delete(AbsFile), % cleanup ok = ftp:recv(Pid, File, Newfile), ok = ftp:delete(Pid, File), % cleanup ok. do_recv_bin(Pid, Config) -> File = ?config(file, Config), Contents1 = "ftp_SUITE test ...", Bin1 = list_to_binary(Contents1), ok = ftp:cd(Pid, "incoming"), ok = ftp:send_bin(Pid, Bin1, File), test_server:sleep(100), {ok, Bin2} = ftp:recv_bin(Pid, File), ok = ftp:delete(Pid, File), % cleanup Contents2 = binary_to_list(Bin2), Contents1 = Contents2, ok. do_recv_chunk(Pid, Config) -> File = ?config(file, Config), Data = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG" "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH" "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Contents1 = lists:flatten(lists:duplicate(10, Data)), Bin1 = list_to_binary(Contents1), ok = ftp:cd(Pid, "incoming"), ok = ftp:type(Pid, binary), ok = ftp:send_bin(Pid, Bin1, File), test_server:sleep(100), {error, "ftp:recv_chunk_start/2 not called"} = recv_chunk(Pid, <<>>), ok = ftp:recv_chunk_start(Pid, File), {ok, Contents2} = recv_chunk(Pid, <<>>), ok = ftp:delete(Pid, File), % cleanup ok = find_diff(Contents2, Contents1, 1), ok. do_type(Pid) -> ok = ftp:type(Pid, ascii), ok = ftp:type(Pid, binary), ok = ftp:type(Pid, ascii), {error, etype} = ftp:type(Pid, foobar), ok. do_quote(Pid) -> ["257 \"/\""++_Rest] = ftp:quote(Pid, "pwd"), %% 257 [_| _] = ftp:quote(Pid, "help"), %% This negativ test causes some ftp servers to hang. This test %% is not important for the client, so we skip it for now. %%["425 Can't build data connection: Connection refused."] %% = ftp:quote(Pid, "list"), ok. watch_dog(Config) -> Dog = test_server:timetrap(inets_test_lib:minutes(1)), NewConfig = lists:keydelete(watchdog, 1, Config), [{watchdog, Dog} | NewConfig]. close_connection(Config) -> case ?config(ftp, Config) of Pid when is_pid(Pid) -> ok = ftp:close(Pid), lists:delete({ftp, Pid}, Config); _ -> Config end. ftp_host(Config) -> case ?config(ftp_remote_host, Config) of undefined -> exit({skip, "No host specified"}); Host -> Host end. check_content(RContent, LContent, Amount) -> LContent2 = case Amount of double -> LContent ++ LContent; singel -> LContent end, case string:equal(RContent, LContent2) of true -> ok; false -> %% Find where the diff is Where = find_diff(RContent, LContent2, 1), Where end. find_diff(A, A, _) -> ok; find_diff([H|T1], [H|T2], Pos) -> find_diff(T1, T2, Pos+1); find_diff(RC, LC, Pos) -> {error, {diff, Pos, RC, LC}}. recv_chunk(Pid, Acc) -> case ftp:recv_chunk(Pid) of ok -> {ok, binary_to_list(Acc)}; {ok, Bin} -> recv_chunk(Pid, <>); Error -> Error end. split(Cs) -> split(Cs, [], []). split([$\r, $\n| Cs], I, Is) -> split(Cs, [], [lists:reverse(I)| Is]); split([C| Cs], I, Is) -> split(Cs, [C| I], Is); split([], I, Is) -> lists:reverse([lists:reverse(I)| Is]). do_ftp_open(Host, Opts) -> p("do_ftp_open -> entry with" "~n Host: ~p" "~n Opts: ~p", [Host, Opts]), case ftp:open(Host, Opts) of {ok, _} = OK -> OK; {error, Reason} -> Str = lists:flatten( io_lib:format("Unable to reach test FTP server ~p (~p)", [Host, Reason])), throw({skip, Str}) end. passwd() -> Host = case inet:gethostname() of {ok, H} -> H; _ -> "localhost" end, "ftp_SUITE@" ++ Host. ftpd_hosts(Config) -> DataDir = ?config(data_dir, Config), FileName = filename:join([DataDir, "../ftp_SUITE_data/", ftpd_hosts]), p("FileName: ~p", [FileName]), case file:consult(FileName) of {ok, [Hosts]} when is_list(Hosts) -> Hosts; _ -> [] end. wrapper(Prefix,doc,Func) -> Prefix++Func(doc); wrapper(_,X,Func) -> Func(X). data_dir(Config) -> case ?config(data_dir, Config) of List when (length(List) > 0) -> PathList = filename:split(List), {NewPathList,_} = lists:split((length(PathList)-1), PathList), DataDir = filename:join(NewPathList ++ [ftp_SUITE_data]), NewConfig = lists:keyreplace(data_dir,1,Config, {data_dir,DataDir}), NewConfig; _ -> Config end. p(F) -> p(F, []). p(F, A) -> case get(ftp_testcase) of undefined -> io:format("~w [~w] " ++ F ++ "~n", [?MODULE, self() | A]); TC when is_atom(TC) -> io:format("~w [~w] ~w:" ++ F ++ "~n", [?MODULE, self(), TC | A]) end.