351 lines
10 KiB
Python
351 lines
10 KiB
Python
|
import os
|
||
|
import pytest
|
||
|
from tempfile import mkdtemp, mkstemp, NamedTemporaryFile
|
||
|
from shutil import rmtree
|
||
|
|
||
|
import numpy.lib._datasource as datasource
|
||
|
from numpy.testing import assert_, assert_equal, assert_raises
|
||
|
|
||
|
import urllib.request as urllib_request
|
||
|
from urllib.parse import urlparse
|
||
|
from urllib.error import URLError
|
||
|
|
||
|
|
||
|
def urlopen_stub(url, data=None):
|
||
|
'''Stub to replace urlopen for testing.'''
|
||
|
if url == valid_httpurl():
|
||
|
tmpfile = NamedTemporaryFile(prefix='urltmp_')
|
||
|
return tmpfile
|
||
|
else:
|
||
|
raise URLError('Name or service not known')
|
||
|
|
||
|
# setup and teardown
|
||
|
old_urlopen = None
|
||
|
|
||
|
|
||
|
def setup_module():
|
||
|
global old_urlopen
|
||
|
|
||
|
old_urlopen = urllib_request.urlopen
|
||
|
urllib_request.urlopen = urlopen_stub
|
||
|
|
||
|
|
||
|
def teardown_module():
|
||
|
urllib_request.urlopen = old_urlopen
|
||
|
|
||
|
# A valid website for more robust testing
|
||
|
http_path = 'http://www.google.com/'
|
||
|
http_file = 'index.html'
|
||
|
|
||
|
http_fakepath = 'http://fake.abc.web/site/'
|
||
|
http_fakefile = 'fake.txt'
|
||
|
|
||
|
malicious_files = ['/etc/shadow', '../../shadow',
|
||
|
'..\\system.dat', 'c:\\windows\\system.dat']
|
||
|
|
||
|
magic_line = b'three is the magic number'
|
||
|
|
||
|
|
||
|
# Utility functions used by many tests
|
||
|
def valid_textfile(filedir):
|
||
|
# Generate and return a valid temporary file.
|
||
|
fd, path = mkstemp(suffix='.txt', prefix='dstmp_', dir=filedir, text=True)
|
||
|
os.close(fd)
|
||
|
return path
|
||
|
|
||
|
|
||
|
def invalid_textfile(filedir):
|
||
|
# Generate and return an invalid filename.
|
||
|
fd, path = mkstemp(suffix='.txt', prefix='dstmp_', dir=filedir)
|
||
|
os.close(fd)
|
||
|
os.remove(path)
|
||
|
return path
|
||
|
|
||
|
|
||
|
def valid_httpurl():
|
||
|
return http_path+http_file
|
||
|
|
||
|
|
||
|
def invalid_httpurl():
|
||
|
return http_fakepath+http_fakefile
|
||
|
|
||
|
|
||
|
def valid_baseurl():
|
||
|
return http_path
|
||
|
|
||
|
|
||
|
def invalid_baseurl():
|
||
|
return http_fakepath
|
||
|
|
||
|
|
||
|
def valid_httpfile():
|
||
|
return http_file
|
||
|
|
||
|
|
||
|
def invalid_httpfile():
|
||
|
return http_fakefile
|
||
|
|
||
|
|
||
|
class TestDataSourceOpen:
|
||
|
def setup_method(self):
|
||
|
self.tmpdir = mkdtemp()
|
||
|
self.ds = datasource.DataSource(self.tmpdir)
|
||
|
|
||
|
def teardown_method(self):
|
||
|
rmtree(self.tmpdir)
|
||
|
del self.ds
|
||
|
|
||
|
def test_ValidHTTP(self):
|
||
|
fh = self.ds.open(valid_httpurl())
|
||
|
assert_(fh)
|
||
|
fh.close()
|
||
|
|
||
|
def test_InvalidHTTP(self):
|
||
|
url = invalid_httpurl()
|
||
|
assert_raises(OSError, self.ds.open, url)
|
||
|
try:
|
||
|
self.ds.open(url)
|
||
|
except OSError as e:
|
||
|
# Regression test for bug fixed in r4342.
|
||
|
assert_(e.errno is None)
|
||
|
|
||
|
def test_InvalidHTTPCacheURLError(self):
|
||
|
assert_raises(URLError, self.ds._cache, invalid_httpurl())
|
||
|
|
||
|
def test_ValidFile(self):
|
||
|
local_file = valid_textfile(self.tmpdir)
|
||
|
fh = self.ds.open(local_file)
|
||
|
assert_(fh)
|
||
|
fh.close()
|
||
|
|
||
|
def test_InvalidFile(self):
|
||
|
invalid_file = invalid_textfile(self.tmpdir)
|
||
|
assert_raises(OSError, self.ds.open, invalid_file)
|
||
|
|
||
|
def test_ValidGzipFile(self):
|
||
|
try:
|
||
|
import gzip
|
||
|
except ImportError:
|
||
|
# We don't have the gzip capabilities to test.
|
||
|
pytest.skip()
|
||
|
# Test datasource's internal file_opener for Gzip files.
|
||
|
filepath = os.path.join(self.tmpdir, 'foobar.txt.gz')
|
||
|
fp = gzip.open(filepath, 'w')
|
||
|
fp.write(magic_line)
|
||
|
fp.close()
|
||
|
fp = self.ds.open(filepath)
|
||
|
result = fp.readline()
|
||
|
fp.close()
|
||
|
assert_equal(magic_line, result)
|
||
|
|
||
|
def test_ValidBz2File(self):
|
||
|
try:
|
||
|
import bz2
|
||
|
except ImportError:
|
||
|
# We don't have the bz2 capabilities to test.
|
||
|
pytest.skip()
|
||
|
# Test datasource's internal file_opener for BZip2 files.
|
||
|
filepath = os.path.join(self.tmpdir, 'foobar.txt.bz2')
|
||
|
fp = bz2.BZ2File(filepath, 'w')
|
||
|
fp.write(magic_line)
|
||
|
fp.close()
|
||
|
fp = self.ds.open(filepath)
|
||
|
result = fp.readline()
|
||
|
fp.close()
|
||
|
assert_equal(magic_line, result)
|
||
|
|
||
|
|
||
|
class TestDataSourceExists:
|
||
|
def setup_method(self):
|
||
|
self.tmpdir = mkdtemp()
|
||
|
self.ds = datasource.DataSource(self.tmpdir)
|
||
|
|
||
|
def teardown_method(self):
|
||
|
rmtree(self.tmpdir)
|
||
|
del self.ds
|
||
|
|
||
|
def test_ValidHTTP(self):
|
||
|
assert_(self.ds.exists(valid_httpurl()))
|
||
|
|
||
|
def test_InvalidHTTP(self):
|
||
|
assert_equal(self.ds.exists(invalid_httpurl()), False)
|
||
|
|
||
|
def test_ValidFile(self):
|
||
|
# Test valid file in destpath
|
||
|
tmpfile = valid_textfile(self.tmpdir)
|
||
|
assert_(self.ds.exists(tmpfile))
|
||
|
# Test valid local file not in destpath
|
||
|
localdir = mkdtemp()
|
||
|
tmpfile = valid_textfile(localdir)
|
||
|
assert_(self.ds.exists(tmpfile))
|
||
|
rmtree(localdir)
|
||
|
|
||
|
def test_InvalidFile(self):
|
||
|
tmpfile = invalid_textfile(self.tmpdir)
|
||
|
assert_equal(self.ds.exists(tmpfile), False)
|
||
|
|
||
|
|
||
|
class TestDataSourceAbspath:
|
||
|
def setup_method(self):
|
||
|
self.tmpdir = os.path.abspath(mkdtemp())
|
||
|
self.ds = datasource.DataSource(self.tmpdir)
|
||
|
|
||
|
def teardown_method(self):
|
||
|
rmtree(self.tmpdir)
|
||
|
del self.ds
|
||
|
|
||
|
def test_ValidHTTP(self):
|
||
|
scheme, netloc, upath, pms, qry, frg = urlparse(valid_httpurl())
|
||
|
local_path = os.path.join(self.tmpdir, netloc,
|
||
|
upath.strip(os.sep).strip('/'))
|
||
|
assert_equal(local_path, self.ds.abspath(valid_httpurl()))
|
||
|
|
||
|
def test_ValidFile(self):
|
||
|
tmpfile = valid_textfile(self.tmpdir)
|
||
|
tmpfilename = os.path.split(tmpfile)[-1]
|
||
|
# Test with filename only
|
||
|
assert_equal(tmpfile, self.ds.abspath(tmpfilename))
|
||
|
# Test filename with complete path
|
||
|
assert_equal(tmpfile, self.ds.abspath(tmpfile))
|
||
|
|
||
|
def test_InvalidHTTP(self):
|
||
|
scheme, netloc, upath, pms, qry, frg = urlparse(invalid_httpurl())
|
||
|
invalidhttp = os.path.join(self.tmpdir, netloc,
|
||
|
upath.strip(os.sep).strip('/'))
|
||
|
assert_(invalidhttp != self.ds.abspath(valid_httpurl()))
|
||
|
|
||
|
def test_InvalidFile(self):
|
||
|
invalidfile = valid_textfile(self.tmpdir)
|
||
|
tmpfile = valid_textfile(self.tmpdir)
|
||
|
tmpfilename = os.path.split(tmpfile)[-1]
|
||
|
# Test with filename only
|
||
|
assert_(invalidfile != self.ds.abspath(tmpfilename))
|
||
|
# Test filename with complete path
|
||
|
assert_(invalidfile != self.ds.abspath(tmpfile))
|
||
|
|
||
|
def test_sandboxing(self):
|
||
|
tmpfile = valid_textfile(self.tmpdir)
|
||
|
tmpfilename = os.path.split(tmpfile)[-1]
|
||
|
|
||
|
tmp_path = lambda x: os.path.abspath(self.ds.abspath(x))
|
||
|
|
||
|
assert_(tmp_path(valid_httpurl()).startswith(self.tmpdir))
|
||
|
assert_(tmp_path(invalid_httpurl()).startswith(self.tmpdir))
|
||
|
assert_(tmp_path(tmpfile).startswith(self.tmpdir))
|
||
|
assert_(tmp_path(tmpfilename).startswith(self.tmpdir))
|
||
|
for fn in malicious_files:
|
||
|
assert_(tmp_path(http_path+fn).startswith(self.tmpdir))
|
||
|
assert_(tmp_path(fn).startswith(self.tmpdir))
|
||
|
|
||
|
def test_windows_os_sep(self):
|
||
|
orig_os_sep = os.sep
|
||
|
try:
|
||
|
os.sep = '\\'
|
||
|
self.test_ValidHTTP()
|
||
|
self.test_ValidFile()
|
||
|
self.test_InvalidHTTP()
|
||
|
self.test_InvalidFile()
|
||
|
self.test_sandboxing()
|
||
|
finally:
|
||
|
os.sep = orig_os_sep
|
||
|
|
||
|
|
||
|
class TestRepositoryAbspath:
|
||
|
def setup_method(self):
|
||
|
self.tmpdir = os.path.abspath(mkdtemp())
|
||
|
self.repos = datasource.Repository(valid_baseurl(), self.tmpdir)
|
||
|
|
||
|
def teardown_method(self):
|
||
|
rmtree(self.tmpdir)
|
||
|
del self.repos
|
||
|
|
||
|
def test_ValidHTTP(self):
|
||
|
scheme, netloc, upath, pms, qry, frg = urlparse(valid_httpurl())
|
||
|
local_path = os.path.join(self.repos._destpath, netloc,
|
||
|
upath.strip(os.sep).strip('/'))
|
||
|
filepath = self.repos.abspath(valid_httpfile())
|
||
|
assert_equal(local_path, filepath)
|
||
|
|
||
|
def test_sandboxing(self):
|
||
|
tmp_path = lambda x: os.path.abspath(self.repos.abspath(x))
|
||
|
assert_(tmp_path(valid_httpfile()).startswith(self.tmpdir))
|
||
|
for fn in malicious_files:
|
||
|
assert_(tmp_path(http_path+fn).startswith(self.tmpdir))
|
||
|
assert_(tmp_path(fn).startswith(self.tmpdir))
|
||
|
|
||
|
def test_windows_os_sep(self):
|
||
|
orig_os_sep = os.sep
|
||
|
try:
|
||
|
os.sep = '\\'
|
||
|
self.test_ValidHTTP()
|
||
|
self.test_sandboxing()
|
||
|
finally:
|
||
|
os.sep = orig_os_sep
|
||
|
|
||
|
|
||
|
class TestRepositoryExists:
|
||
|
def setup_method(self):
|
||
|
self.tmpdir = mkdtemp()
|
||
|
self.repos = datasource.Repository(valid_baseurl(), self.tmpdir)
|
||
|
|
||
|
def teardown_method(self):
|
||
|
rmtree(self.tmpdir)
|
||
|
del self.repos
|
||
|
|
||
|
def test_ValidFile(self):
|
||
|
# Create local temp file
|
||
|
tmpfile = valid_textfile(self.tmpdir)
|
||
|
assert_(self.repos.exists(tmpfile))
|
||
|
|
||
|
def test_InvalidFile(self):
|
||
|
tmpfile = invalid_textfile(self.tmpdir)
|
||
|
assert_equal(self.repos.exists(tmpfile), False)
|
||
|
|
||
|
def test_RemoveHTTPFile(self):
|
||
|
assert_(self.repos.exists(valid_httpurl()))
|
||
|
|
||
|
def test_CachedHTTPFile(self):
|
||
|
localfile = valid_httpurl()
|
||
|
# Create a locally cached temp file with an URL based
|
||
|
# directory structure. This is similar to what Repository.open
|
||
|
# would do.
|
||
|
scheme, netloc, upath, pms, qry, frg = urlparse(localfile)
|
||
|
local_path = os.path.join(self.repos._destpath, netloc)
|
||
|
os.mkdir(local_path, 0o0700)
|
||
|
tmpfile = valid_textfile(local_path)
|
||
|
assert_(self.repos.exists(tmpfile))
|
||
|
|
||
|
|
||
|
class TestOpenFunc:
|
||
|
def setup_method(self):
|
||
|
self.tmpdir = mkdtemp()
|
||
|
|
||
|
def teardown_method(self):
|
||
|
rmtree(self.tmpdir)
|
||
|
|
||
|
def test_DataSourceOpen(self):
|
||
|
local_file = valid_textfile(self.tmpdir)
|
||
|
# Test case where destpath is passed in
|
||
|
fp = datasource.open(local_file, destpath=self.tmpdir)
|
||
|
assert_(fp)
|
||
|
fp.close()
|
||
|
# Test case where default destpath is used
|
||
|
fp = datasource.open(local_file)
|
||
|
assert_(fp)
|
||
|
fp.close()
|
||
|
|
||
|
def test_del_attr_handling():
|
||
|
# DataSource __del__ can be called
|
||
|
# even if __init__ fails when the
|
||
|
# Exception object is caught by the
|
||
|
# caller as happens in refguide_check
|
||
|
# is_deprecated() function
|
||
|
|
||
|
ds = datasource.DataSource()
|
||
|
# simulate failed __init__ by removing key attribute
|
||
|
# produced within __init__ and expected by __del__
|
||
|
del ds._istmpdest
|
||
|
# should not raise an AttributeError if __del__
|
||
|
# gracefully handles failed __init__:
|
||
|
ds.__del__()
|