More tests, change file to filename

This commit is contained in:
Simeon Warner 2016-03-26 23:08:24 -04:00
parent 750ec878f2
commit b5217b9eed
2 changed files with 121 additions and 32 deletions

View File

@ -194,18 +194,18 @@ class Client(object):
num_deleted=0 num_deleted=0
for resource in created: for resource in created:
uri = resource.uri uri = resource.uri
file = self.mapper.src_to_dst(uri) filename = self.mapper.src_to_dst(uri)
self.logger.info("created: %s -> %s" % (uri,file)) self.logger.info("created: %s -> %s" % (uri,filename))
num_created+=self.update_resource(resource,file,'created') num_created+=self.update_resource(resource,filename,'created')
for resource in updated: for resource in updated:
uri = resource.uri uri = resource.uri
file = self.mapper.src_to_dst(uri) filename = self.mapper.src_to_dst(uri)
self.logger.info("updated: %s -> %s" % (uri,file)) self.logger.info("updated: %s -> %s" % (uri,filename))
num_updated+=self.update_resource(resource,file,'updated') num_updated+=self.update_resource(resource,filename,'updated')
for resource in deleted: for resource in deleted:
uri = resource.uri uri = resource.uri
file = self.mapper.src_to_dst(uri) filename = self.mapper.src_to_dst(uri)
num_deleted+=self.delete_resource(resource,file,allow_deletion) num_deleted+=self.delete_resource(resource,filename,allow_deletion)
### 6. Store last timestamp to allow incremental sync ### 6. Store last timestamp to allow incremental sync
if (not audit_only and self.last_timestamp>0): if (not audit_only and self.last_timestamp>0):
ClientState().set_state(self.sitemap,self.last_timestamp) ClientState().set_state(self.sitemap,self.last_timestamp)
@ -316,17 +316,17 @@ class Client(object):
num_created = 0 num_created = 0
for resource in src_change_list: for resource in src_change_list:
uri = resource.uri uri = resource.uri
file = self.mapper.src_to_dst(uri) filename = self.mapper.src_to_dst(uri)
if (resource.change == 'updated'): if (resource.change == 'updated'):
self.logger.info("updated: %s -> %s" % (uri,file)) self.logger.info("updated: %s -> %s" % (uri,filename))
self.update_resource(resource,file,'updated') self.update_resource(resource,filename,'updated')
num_updated+=1 num_updated+=1
elif (resource.change == 'created'): elif (resource.change == 'created'):
self.logger.info("created: %s -> %s" % (uri,file)) self.logger.info("created: %s -> %s" % (uri,filename))
self.update_resource(resource,file,'created') self.update_resource(resource,filename,'created')
num_created+=1 num_created+=1
elif (resource.change == 'deleted'): elif (resource.change == 'deleted'):
num_deleted+=self.delete_resource(resource,file,allow_deletion) num_deleted+=self.delete_resource(resource,filename,allow_deletion)
else: else:
raise ClientError("Unknown change type %s" % (resource.change) ) raise ClientError("Unknown change type %s" % (resource.change) )
### 7. Report status and planned actions ### 7. Report status and planned actions
@ -339,8 +339,8 @@ class Client(object):
### 9. Done ### 9. Done
self.logger.debug("Completed incremental sync") self.logger.debug("Completed incremental sync")
def update_resource(self, resource, file, change=None): def update_resource(self, resource, filename, change=None):
"""Update resource from uri to file on local system. """Update resource from uri to filename on local system.
Update means three things: Update means three things:
1. GET resources 1. GET resources
@ -355,15 +355,15 @@ class Client(object):
Returns the number of resources updated/created (0 or 1) Returns the number of resources updated/created (0 or 1)
""" """
path = os.path.dirname(file) path = os.path.dirname(filename)
distutils.dir_util.mkpath(path) distutils.dir_util.mkpath(path)
num_updated=0 num_updated=0
if (self.dryrun): if (self.dryrun):
self.logger.info("dryrun: would GET %s --> %s" % (resource.uri,file)) self.logger.info("dryrun: would GET %s --> %s" % (resource.uri,filename))
else: else:
# 1. GET # 1. GET
try: try:
urlretrieve(resource.uri,file) urlretrieve(resource.uri,filename)
num_updated+=1 num_updated+=1
except IOError as e: except IOError as e:
msg = "Failed to GET %s -- %s" % (resource.uri,str(e)) msg = "Failed to GET %s -- %s" % (resource.uri,str(e))
@ -375,22 +375,22 @@ class Client(object):
# 2. set timestamp if we have one # 2. set timestamp if we have one
if (resource.timestamp is not None): if (resource.timestamp is not None):
unixtime = int(resource.timestamp) #no fractional unixtime = int(resource.timestamp) #no fractional
os.utime(file,(unixtime,unixtime)) os.utime(filename,(unixtime,unixtime))
if (resource.timestamp > self.last_timestamp): if (resource.timestamp > self.last_timestamp):
self.last_timestamp = resource.timestamp self.last_timestamp = resource.timestamp
self.log_event(Resource(resource=resource, change=change)) self.log_event(Resource(resource=resource, change=change))
# 3. sanity check # 3. sanity check
length = os.stat(file).st_size length = os.stat(filename).st_size
if (resource.length != length): if (resource.length is not None and resource.length != length):
self.logger.info("Downloaded size for %s of %d bytes does not match expected %d bytes" % (resource.uri,length,resource.length)) self.logger.info("Downloaded size for %s of %d bytes does not match expected %d bytes" % (resource.uri,length,resource.length))
if (self.checksum and resource.md5 is not None): if (self.checksum and resource.md5 is not None):
file_md5 = compute_md5_for_file(file) file_md5 = compute_md5_for_file(filename)
if (resource.md5 != file_md5): if (resource.md5 != file_md5):
self.logger.info("MD5 mismatch for %s, got %s but expected %s bytes" % (resource.uri,file_md5,resource.md5)) self.logger.info("MD5 mismatch for %s, got %s but expected %s bytes" % (resource.uri,file_md5,resource.md5))
return(num_updated) return(num_updated)
def delete_resource(self, resource, file, allow_deletion=False): def delete_resource(self, resource, filename, allow_deletion=False):
"""Delete copy of resource in file on local system. """Delete copy of resource in filename on local system.
Will only actually do the deletion if allow_deletion is True. Regardless Will only actually do the deletion if allow_deletion is True. Regardless
of whether the deletion occurs, self.last_timestamp will be updated of whether the deletion occurs, self.last_timestamp will be updated
@ -405,20 +405,20 @@ class Client(object):
self.last_timestamp = resource.timestamp self.last_timestamp = resource.timestamp
if (allow_deletion): if (allow_deletion):
if (self.dryrun): if (self.dryrun):
self.logger.info("dryrun: would delete %s -> %s" % (uri,file)) self.logger.info("dryrun: would delete %s -> %s" % (uri,filename))
else: else:
try: try:
os.unlink(file) os.unlink(filename)
num_deleted+=1 num_deleted+=1
self.logger.info("deleted: %s -> %s" % (uri,filename))
self.log_event(Resource(resource=resource, change="deleted"))
except OSError as e: except OSError as e:
msg = "Failed to DELETE %s -> %s : %s" % (uri,file,str(e)) msg = "Failed to DELETE %s -> %s : %s" % (uri,filename,str(e))
#if (self.ignore_failures): #if (self.ignore_failures):
self.logger.warning(msg) self.logger.warning(msg)
# return # return
#else: #else:
# raise ClientFatalError(msg) # raise ClientFatalError(msg)
self.logger.info("deleted: %s -> %s" % (uri,file))
self.log_event(Resource(resource=resource, change="deleted"))
else: else:
self.logger.info("nodelete: would delete %s (--delete to enable)" % uri) self.logger.info("nodelete: would delete %s (--delete to enable)" % uri)
return(num_deleted) return(num_deleted)

View File

@ -9,6 +9,7 @@ import sys
import os.path import os.path
from resync.client import Client, ClientFatalError from resync.client import Client, ClientFatalError
from resync.resource import Resource
from resync.resource_list import ResourceList from resync.resource_list import ResourceList
from resync.change_list import ChangeList from resync.change_list import ChangeList
@ -34,6 +35,78 @@ class TestClient(TestCase):
self.assertEqual( c.sitemap_uri('/abcd2'), '/abcd2' ) self.assertEqual( c.sitemap_uri('/abcd2'), '/abcd2' )
self.assertEqual( c.sitemap_uri('scheme:/abcd3'), 'scheme:/abcd3' ) self.assertEqual( c.sitemap_uri('scheme:/abcd3'), 'scheme:/abcd3' )
def test18_update_resource(self):
c = Client()
resource = Resource(uri='http://example.org/dir/2')
filename = os.path.join(self.tmpdir,'dir/resource2')
# dryrun
with LogCapture() as lc:
c.dryrun = True
c.logger = logging.getLogger('resync.client')
n = c.update_resource( resource, filename )
self.assertEqual( n, 0 )
self.assertTrue( lc.records[-1].msg.startswith('dryrun: would GET http://example.org/dir/2 ') )
c.dryrun = False
# get from file uri that does not exist
resource = Resource(uri='tests/testdata/i_do_not_exist')
with LogCapture() as lc:
c.logger = logging.getLogger('resync.client')
self.assertRaises( ClientFatalError, c.update_resource, resource, filename )
# get from file uri
resource = Resource(uri='tests/testdata/examples_from_spec/resourcesync_ex_1.xml')
with LogCapture() as lc:
c.logger = logging.getLogger('resync.client')
n = c.update_resource( resource, filename )
self.assertEqual( n, 1 )
self.assertTrue( lc.records[-1].msg.startswith('Event: {') )
def test19_delete_resource(self):
c = Client()
resource = Resource(uri='http://example.org/1')
filename = os.path.join(self.tmpdir,'resource1')
c.last_timestamp = 5
# no delete, no timestamp update
with LogCapture() as lc:
c.logger = logging.getLogger('resync.client')
n = c.delete_resource( resource, filename )
self.assertEqual( n, 0 )
self.assertEqual( lc.records[-1].msg,
'nodelete: would delete http://example.org/1 (--delete to enable)' )
self.assertEqual( c.last_timestamp, 5 )
# no delete but timestamp update
resource.timestamp = 10
with LogCapture() as lc:
c.logger = logging.getLogger('resync.client')
n = c.delete_resource( resource, filename )
self.assertEqual( n, 0 )
self.assertEqual( lc.records[-1].msg,
'nodelete: would delete http://example.org/1 (--delete to enable)' )
self.assertEqual( c.last_timestamp, 10 )
# allow delete but dryrun
with LogCapture() as lc:
c.dryrun = True
c.logger = logging.getLogger('resync.client')
n = c.delete_resource( resource, filename, allow_deletion=True )
self.assertEqual( n, 0 )
self.assertTrue( lc.records[-1].msg.startswith('dryrun: would delete http://example.org/1') )
c.dryrun = False
# allow delete but no resource present
with LogCapture() as lc:
c.logger = logging.getLogger('resync.client')
n = c.delete_resource( resource, filename, allow_deletion=True )
self.assertEqual( n, 0 )
self.assertTrue( lc.records[-1].msg.startswith('Failed to DELETE http://example.org/1') )
# successful deletion, first make file...
with open(filename, 'w') as fh:
fh.write('delete me')
fh.close()
with LogCapture() as lc:
c.logger = logging.getLogger('resync.client')
n = c.delete_resource( resource, filename, allow_deletion=True )
self.assertEqual( n, 1 )
self.assertTrue( lc.records[-1].msg.startswith('Event: {') )
self.assertTrue( lc.records[-2].msg.startswith('deleted: http://example.org/1 ->') )
def test20_parse_document(self): def test20_parse_document(self):
# Key property of the parse_document() method is that it parses the # Key property of the parse_document() method is that it parses the
# document and identifies its type # document and identifies its type
@ -54,6 +127,18 @@ class TestClient(TestCase):
c.sitemap_name='tests/testdata/examples_from_spec/resourcesync_ex_22.xml' c.sitemap_name='tests/testdata/examples_from_spec/resourcesync_ex_22.xml'
c.parse_document() c.parse_document()
self.assertTrue( re.search(r'Parsed changedump document with 3 entries',capturer.result) ) self.assertTrue( re.search(r'Parsed changedump document with 3 entries',capturer.result) )
# Document that doesn't exist
c.sitemap_name='/does_not_exist'
self.assertRaises( ClientFatalError, c.parse_document )
# and verbose with truncation...
with capture_stdout() as capturer:
c.verbose = True
c.sitemap_name = 'tests/testdata/examples_from_spec/resourcesync_ex_1.xml'
c.max_sitemap_entries = 1
c.parse_document()
self.assertTrue( re.search(r'Showing first 1 entries', capturer.result ) )
self.assertTrue( re.search(r'\[0\] ', capturer.result ) )
self.assertFalse( re.search(r'\[1\] ', capturer.result ) )
def test40_write_resource_list_mappings(self): def test40_write_resource_list_mappings(self):
c = Client() c = Client()
@ -70,13 +155,17 @@ class TestClient(TestCase):
def test41_write_resource_list_path(self): def test41_write_resource_list_path(self):
c = Client() c = Client()
c.set_mappings( ['http://example.org/','tests/testdata/'] ) c.set_mappings( ['http://example.org/','tests/testdata/'] )
links=[{'rel':'uri_c','href':'uri_d'}]
# with an explicit paths setting only the specified paths will be included # with an explicit paths setting only the specified paths will be included
with capture_stdout() as capturer: with capture_stdout() as capturer:
c.write_resource_list(paths='tests/testdata/dir1') c.write_resource_list(paths='tests/testdata/dir1', links=links)
self.assertTrue( re.search(r'<rs:md at="\S+" capability="resourcelist"', capturer.result ) ) self.assertTrue( re.search(r'<rs:md at="\S+" capability="resourcelist"', capturer.result ) )
self.assertTrue( re.search(r'<url><loc>http://example.org/dir1/file_a</loc>', capturer.result ) ) self.assertTrue( re.search(r'<url><loc>http://example.org/dir1/file_a</loc>', capturer.result ) )
self.assertTrue( re.search(r'<url><loc>http://example.org/dir1/file_b</loc>', capturer.result ) ) self.assertTrue( re.search(r'<url><loc>http://example.org/dir1/file_b</loc>', capturer.result ) )
self.assertFalse( re.search(r'<url><loc>http://example.org/dir2/file_x</loc>', capturer.result ) ) self.assertFalse( re.search(r'<url><loc>http://example.org/dir2/file_x</loc>', capturer.result ) )
# check link present
self.assertTrue( re.search(r'rel="uri_c"', capturer.result ) )
self.assertTrue( re.search(r'href="uri_d"', capturer.result ) )
# Travis CI does not preserve timestamps from github so test here for the file # Travis CI does not preserve timestamps from github so test here for the file
# size but not the datestamp # size but not the datestamp
#self.assertTrue( re.search(r'<url><loc>http://example.org/dir1/file_a</loc><lastmod>[\w\-:]+</lastmod><rs:md length="20" /></url>', capturer.result ) ) #self.assertTrue( re.search(r'<url><loc>http://example.org/dir1/file_a</loc><lastmod>[\w\-:]+</lastmod><rs:md length="20" /></url>', capturer.result ) )
@ -91,7 +180,7 @@ class TestClient(TestCase):
self.assertFalse( os.path.exists(outfile) ) self.assertFalse( os.path.exists(outfile) )
c.write_resource_list(paths='tests/testdata/dir1', dump=True) c.write_resource_list(paths='tests/testdata/dir1', dump=True)
self.assertTrue( os.path.getsize(outfile)>100 ) self.assertTrue( os.path.getsize(outfile)>100 )
# (specific fuile) # (specific file)
outbase = os.path.join(self.tmpdir,'rl_out_dump') outbase = os.path.join(self.tmpdir,'rl_out_dump')
outfile = outbase+'00000.zip' outfile = outbase+'00000.zip'
self.assertFalse( os.path.exists(outfile) ) self.assertFalse( os.path.exists(outfile) )