From 16ce2ef88380dcad85e2edfda957b46ec9a29948 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 23 Feb 2014 12:37:47 -0500 Subject: [PATCH] More negative tests, pylint work. #175 --- glymur/jp2box.py | 17 ++++++--- glymur/jp2k.py | 17 ++++++++- glymur/test/test_jp2box_jpx.py | 65 ++++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f82151a..18f994d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -124,7 +124,7 @@ class Jp2kBox(object): fptr.write(struct.pack('>I', end_pos - orig_pos)) fptr.seek(end_pos) - def parse_this_box(self, fptr, box_id, start, num_bytes): + def _parse_this_box(self, fptr, box_id, start, num_bytes): """Parse the current box. Parameters @@ -214,7 +214,7 @@ class Jp2kBox(object): # The box_length value really is the length of the box! num_bytes = box_length - box = self.parse_this_box(fptr, box_id, start, num_bytes) + box = self._parse_this_box(fptr, box_id, start, num_bytes) superbox.append(box) @@ -560,7 +560,8 @@ class ChannelDefinitionBox(Jp2kBox): def _validate(self): """Verify that the box obeys the specifications.""" # channel type and association must be specified. - if not (len(self.index) == len(self.channel_type) == len(self.association)): + if not ((len(self.index) == len(self.channel_type)) and + (len(self.channel_type) == len(self.association))): msg = "Length of channel definition box inputs must be the same." raise IOError(msg) @@ -953,10 +954,18 @@ class DataReferenceBox(Jp2kBox): msg += 'entry URL boxes.' raise IOError(msg) + def _write_validate(self): + """Verify that the box obeys the specifications for writing. + """ + if len(self.DR) == 0: + msg = "A data reference box cannot be empty when written to a file." + raise IOError(msg) + self._validate() + def write(self, fptr): """Write a Data Reference box to file. """ - self._validate() + self._write_validate() # Very similar to the say a superbox is written. orig_pos = fptr.tell() diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 96b6e68..988e814 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1110,6 +1110,10 @@ def _validate_nonzero_image_size(nrows, ncols, component_index): raise IOError(msg) +JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', + 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', + 'uuid'] + def _validate_jp2_box_sequence(boxes): """Run through series of tests for JP2 box legality. @@ -1118,6 +1122,18 @@ def _validate_jp2_box_sequence(boxes): _validate_signature_compatibility(boxes) _validate_jp2h(boxes) _validate_jp2c(boxes) + if boxes[1].brand == 'jpx ': + _validate_jpx_box_sequence(boxes) + else: + count = _collect_box_count(boxes) + for id in count.keys(): + if id not in JP2_IDS: + msg = "The presence of a '{0}' box requires that the file type " + msg += "brand be set to 'jpx '." + raise IOError(msg.format(id)) + +def _validate_jpx_box_sequence(boxes): + """Run through series of tests for JPX box legality.""" _validate_association(boxes) _validate_label(boxes) _validate_jpx_brand(boxes, boxes[1].brand) @@ -1125,7 +1141,6 @@ def _validate_jp2_box_sequence(boxes): _validate_singletons(boxes) _validate_top_level(boxes) - def _validate_signature_compatibility(boxes): """Validate the file signature and compatibility status.""" # Check for a bad sequence of boxes. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index b2916c5..e50d4f9 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -4,12 +4,10 @@ Test suite specifically targeting JPX box layout. """ import os -import shutil import struct import sys import tempfile import unittest -import warnings import xml.etree.cElementTree as ET import glymur @@ -41,6 +39,18 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_jp2_with_jpx_box(self): + """If the brand is jp2, then no jpx boxes are allowed.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + boxes = jp2.box + + boxes.append(glymur.jp2box.AssociationBox()) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jp2.wrap(tfile.name, boxes=boxes) + def test_ftbl(self): """Write a fragment table box.""" # Add a negative test where offset < 0 @@ -128,17 +138,18 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[-1].box[2].label, label) def test_empty_data_reference(self): - """Data reference boxes can be empty.""" + """Empty data reference boxes can be created, but not written.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + boxes[1].brand = 'jpx ' + dref = glymur.jp2box.DataReferenceBox() boxes.append(dref) with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - self.assertEqual(jpx.box[-1].box_id, 'dtbl') - self.assertEqual(len(jpx.box[-1].box), 0) + with self.assertRaises(IOError): + jp2.wrap(tfile.name, boxes=boxes) def test_deurl_child_of_dtbl(self): """Data reference boxes can only contain data entry url boxes.""" @@ -157,13 +168,16 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_only_one_data_reference(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' + flag = 0 version = (0, 0, 0) url = 'file:////usr/local/bin' @@ -174,16 +188,16 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_lbl_at_top_level(self): """Label boxes can only be inside a asoc box .""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - flag = 0 - version = (0, 0, 0) - url = 'file:////usr/local/bin' + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' + lblb = glymur.jp2box.LabelBox('hi there') # Put it inside the jp2 header box. @@ -191,13 +205,16 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) - def test_data_reference_not_at_top_level(self): + def test_data_reference_in_subbox(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' + flag = 0 version = (0, 0, 0) url = 'file:////usr/local/bin' @@ -209,13 +226,17 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_jp2_to_jpx_sans_jp2_compatibility(self): """jp2 wrapped to jpx not including jp2 compatibility is wrong.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' boxes[1].compatibility_list.append('jp2 ') + numbers = [0, 1] nlst = glymur.jp2box.NumberListBox(numbers) the_xml = ET.fromstring('0') @@ -225,7 +246,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(RuntimeError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_jp2_to_jpx_sans_jpx_brand(self): """Verify error when jp2 wrapped to jpx does not include jpx brand.""" @@ -241,7 +262,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(RuntimeError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -262,7 +283,7 @@ class TestJPX(unittest.TestCase): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + flst.write(tfile) def test_flst_offsets_not_positive(self): """A fragment list box offsets must be positive.""" @@ -272,7 +293,7 @@ class TestJPX(unittest.TestCase): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + flst.write(tfile) def test_flst_lengths_not_positive(self): """A fragment list box lengths must be positive.""" @@ -282,14 +303,14 @@ class TestJPX(unittest.TestCase): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + flst.write(tfile) def test_ftbl_boxes_empty(self): """A fragment table box must have at least one child box.""" ftbl = glymur.jp2box.FragmentTableBox() with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftbl.write(tfile) + ftbl.write(tfile) def test_ftbl_child_not_flst(self): """A fragment table box can only contain a fragment list.""" @@ -297,7 +318,7 @@ class TestJPX(unittest.TestCase): ftbl = glymur.jp2box.FragmentTableBox(box=[free]) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftbl.write(tfile) + ftbl.write(tfile) def test_jpx_rreq_mask_length_3(self): """There are some JPX files with rreq mask length of 3.""" @@ -314,7 +335,7 @@ class TestJPX(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - + # Add the header for an unknwon superbox. write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) tfile.write(write_buffer)