source: test/test_public_servers.py @ 1660:93ea351f922b

Last change on this file since 1660:93ea351f922b was 1660:93ea351f922b, checked in by Stefan Schwarzer <sschwarzer@…>, 3 years ago
Remove `unittest.main` calls Since we now use pytest as test runner and not a shell loop in the makefile, there's no longer a need for explicit `unittest.main` calls. ticket: 98
File size: 7.1 KB
Line 
1# Copyright (C) 2009-2016, Stefan Schwarzer <sschwarzer@sschwarzer.net>
2# and ftputil contributors (see `doc/contributors.txt`)
3# See the file LICENSE for licensing terms.
4
5from __future__ import print_function
6from __future__ import unicode_literals
7
8import os
9import socket
10import subprocess
11import unittest
12
13import ftputil
14
15import test
16
17
18def email_address():
19    """
20    Return the email address used to identify the client to an
21    FTP server.
22
23    If the hostname is "warpy", use my (Stefan's) email address,
24    else try to use the content of the `$EMAIL` environment variable.
25    If that doesn't exist, use a dummy address.
26    """
27    hostname = socket.gethostname()
28    if hostname == "warpy":
29        email = "sschwarzer@sschwarzer.net"
30    else:
31        dummy_address = "anonymous@example.com"
32        email = os.environ.get("EMAIL", dummy_address)
33        if not email:
34            # Environment variable exists but content is an empty string
35            email = dummy_address
36    return email
37
38EMAIL = email_address()
39
40
41def ftp_client_listing(server, directory):
42    """
43    Log into the FTP server `server` using the command line client,
44    then change to the `directory` and retrieve a listing with "dir".
45    Return the list of items found as an `os.listdir` would return it.
46    """
47    # The `-n` option prevents an auto-login.
48    ftp_popen = subprocess.Popen(["ftp", "-n", server],
49                                 stdin=subprocess.PIPE,
50                                 stdout=subprocess.PIPE,
51                                 universal_newlines=True)
52    commands = ["user anonymous {0}".format(EMAIL), "dir", "bye"]
53    if directory:
54        # Change to this directory before calling "dir".
55        commands.insert(1, "cd {0}".format(directory))
56    input_ = "\n".join(commands)
57    stdout, unused_stderr = ftp_popen.communicate(input_)
58    # Collect the directory/file names from the listing's text
59    names = []
60    for line in stdout.strip().split("\n"):
61        if line.startswith("total ") or line.startswith("Trying "):
62            continue
63        parts = line.split()
64        if parts[-2] == "->":
65            # Most likely a link
66            name = parts[-3]
67        else:
68            name = parts[-1]
69        names.append(name)
70    # Remove entries for current and parent directory since they
71    # aren't included in the result of `FTPHost.listdir` either.
72    names = [name for name in names
73                  if name not in (".", "..")]
74    return names
75
76
77class TestPublicServers(unittest.TestCase):
78    """
79    Get directory listings from various public FTP servers
80    with a command line client and ftputil and compare both.
81
82    An important aspect is to test different "spellings" of
83    the same directory. For example, to list the root directory
84    which is usually set after login, use "" (nothing), ".",
85    "/", "/.", "./.", "././", "..", "../.", "../.." etc.
86
87    The command line client `ftp` has to be in the path.
88    """
89
90    # Implementation note:
91    #
92    # I (Stefan) implement the code so it works with Ubuntu's
93    # client. Other clients may work or not. If you have problems
94    # testing some other client, please send me a (small) patch.
95    # Keep in mind that I don't plan supporting as many FTP
96    # obscure commandline clients as servers. ;-)
97
98    # List of pairs with server name and a directory "guaranteed
99    # to exist" under the login directory which is assumed to be
100    # the root directory.
101    servers = [# Posix format
102               ("ftp.debian.org", "debian"),
103               ("ftp.gnome.org",  "pub"),
104               ("ftp.heanet.ie",  "pub"),
105               ("ftp.heise.de",   "pub"),
106               # DOS/Microsoft format
107               # ftp.microsoft.com sporadically refuses anonymous access
108               # ("530 User cannot log in, home directory inaccessible")
109               #("ftp.microsoft.com", "deskapps"),
110              ]
111
112    # This data structure contains the initial directories "." and
113    # "DIR" (which will be replaced by a valid directory name for
114    # each server). The list after the initial directory contains
115    # paths that will be queried after changing into the initial
116    # directory. All items in these lists are actually supposed to
117    # yield the same directory contents.
118    paths_table = [
119      (".", ["", ".", "/", "/.", "./.", "././", "..", "../.", "../..",
120             "DIR/..", "/DIR/../.", "/DIR/../.."]),
121      ("DIR", ["", ".", "/DIR", "/DIR/", "../DIR", "../../DIR"])
122      ]
123
124    def inner_test_server(self, server, initial_directory, paths):
125        """
126        Test one server for one initial directory.
127
128        Connect to the server `server`; if the string argument
129        `initial_directory` has a true value, change to this
130        directory. Then iterate over all strings in the sequence
131        `paths`, comparing the results of a listdir call with the
132        listing from the command line client.
133        """
134        canonical_names = ftp_client_listing(server, initial_directory)
135        host = ftputil.FTPHost(server, "anonymous", EMAIL)
136        try:
137            host.chdir(initial_directory)
138            for path in paths:
139                path = path.replace("DIR", initial_directory)
140                # Make sure that we don't recycle directory entries, i. e.
141                # really repeatedly retrieve the directory contents
142                # (shouldn't happen anyway with the current implementation).
143                host.stat_cache.clear()
144                names = host.listdir(path)
145                # Filter out "hidden" names since the FTP command line
146                # client won't include them in its listing either.
147                names = [name for name in names
148                              if not (
149                                name.startswith(".") or
150                                # The login directory of `ftp.microsoft.com`
151                                # contains this "hidden" entry that ftputil
152                                # finds but not the FTP command line client.
153                                name == "mscomtest"
154                              )]
155                failure_message = ("For server {0}, directory {1}: {2} != {3}".
156                                   format(server, initial_directory, names,
157                                          canonical_names))
158                assert names == canonical_names, failure_message
159        finally:
160            host.close()
161
162    @test.skip_long_running_test
163    def test_servers(self):
164        """
165        Test all servers in `self.servers`.
166
167        For each server, get the listings for the login directory and
168        one other directory which is known to exist. Use different
169        "spellings" to retrieve each list via ftputil and compare with
170        the results gotten with the command line client.
171        """
172        for server, actual_initial_directory in self.servers:
173            for initial_directory, paths in self.paths_table:
174                initial_directory = initial_directory.replace(
175                                      "DIR", actual_initial_directory)
176                print(server, initial_directory)
177                self.inner_test_server(server, initial_directory, paths)
Note: See TracBrowser for help on using the repository browser.