14.7. COBOL Schema Testing

Processing COBOL (and possibly EBCDIC) files requires a more sophisticated schema. And a more sophisticated schema loader.

See The COBOL Package.

14.7.1. Overheads

"""stingray.cobol Unit Tests."""
import unittest
import decimal
import weakref
import logging, sys
import io
import types

import stingray.cobol
from stingray.cobol.loader import Picture

14.7.2. Handy Mocks

class MockDefaultCell:
    def __init__( self, value, workbook, attr ):
        self.value= value
        self.workbook= workbook
        sekf.attr= attr
    def __eq__( self, other ):
        return all( (
            self.__class__ == other.__class__,
            self.value == other.value,
            self.attr == other.attr
            ) )
    def __ne__( self, other ):
        return not self.__eq__( other )

class MockDDE:
    def __init__( self, **kw ):
        self.totalSize= 0 # if otherwise unspecified
        self.kw= kw
        self.__dict__.update( kw )
        self.children=[]
    def __repr__( self ):
        nv= ", ".join( "{0}={1!r}".format( k, self.kw[k] ) for k in self.kw )
        return "MockDDE( {1} )".format( self.children, nv )
    def addChild( self, child ):
        self.children.append( child )
        self.totalSize += child.totalSize

class MockNonRedefine:
    def __init__( self, **kw ):
        self.__dict__.update( kw )

class MockOccurs:
    def __init__( self, number ):
        self._number= number
    def number( self, aRow ):
        return self._number
    def __repr__( self ):
        return "{_class_name}({_number})".format(_class_name=self.__class__.__name__, **self.__dict__)

class MockSchema( list ):
    def __init__( self, *args, **kw ):
        super().__init__( *args )
        self.info= kw
    def lrecl( self ):
        return max( a.offset+a.size for a in self )

14.7.3. Repeating Attribute

We subclass schema.Attribute to make it depend on MockDefaultCell instead of some working class in cell.

class AttributeFixture( stingray.cobol.RepeatingAttribute ):
    default_cell= MockDefaultCell

Now we can test simple DDE’s without an occurs clause.

class TestNonRepeatingAttribute( unittest.TestCase ):
    def setUp( self ):
        self.simple_dde= MockDDE( level="05", name="SOME-COLUMN", occurs=MockOccurs(1),
            size=5, totalSize=5, offset=0 )
        self.simple_dde.dimensionality= ()
        self.simple= AttributeFixture(
            name="SOME-COLUMN", dde=weakref.ref(self.simple_dde),
            size=5, create=MockDefaultCell,  )
    def test_should_have_names( self ):
        self.assertEqual( "SOME-COLUMN", self.simple.name )
    def test_should_not_index_simple( self ):
        try:
            simple= self.simple.index(2)
            self.fail()
        except IndexError:
            pass

And we can test simple DDE’s with an occurs clause.

class TestRepeatingAttribute( unittest.TestCase ):
    def setUp( self ):
        self.dde= MockDDE( level="05", name="REPEAT", occurs=MockOccurs(4), size=3, totalSize=12, offset=5)
        self.dde.dimensionality=(self.dde,)
        self.complex= AttributeFixture(
            name="REPEAT", dde=weakref.ref(self.dde),
            create=MockDefaultCell, occurs=4, size=3, totalSize=12, )
    def test_should_have_names( self ):
        self.assertEqual( "REPEAT", self.complex.name )
    def test_should_index_complex( self ):
        """1-based indexing in COBOL is ignored here."""
        complex= self.complex.index(0)
        self.assertEqual( 5, complex.offset )
        self.assertEqual( 3, complex.size )
        complex= self.complex.index(1)
        self.assertEqual( 8, complex.offset )
        self.assertEqual( 3, complex.size )
        complex= self.complex.index(2)
        self.assertEqual( 11, complex.offset )
        self.assertEqual( 3, complex.size )
        complex= self.complex.index(3) # out of range!
        self.assertEqual( 14, complex.offset )
        self.assertEqual( 3, complex.size )

And we can test nested DDE’s with multiple occurs clauses.

class TestNestedRepeatingAttribute( unittest.TestCase ):
    def setUp( self ):
        self.child= MockDDE( level="10", name="ITEM",
            occurs=MockOccurs(4), size=2, totalSize=8, offset=5,
            )
        self.parent= MockDDE( level="05", name="REPEAT",
            occurs=MockOccurs(5), size=8, totalSize=30,  offset=5, children=[self.child],
            )
        self.parent.dimensionality=(self.parent,)
        self.child.dimensionality=(self.parent,self.child)
        self.complex_05= AttributeFixture(
            name="REPEAT", dde=weakref.ref(self.parent),
            create=MockDefaultCell, occurs=5, size=8, totalSize=40, )
        self.complex_10= AttributeFixture(
            name="ITEM", dde=weakref.ref(self.child),
            create=MockDefaultCell, occurs=4, size=2, totalSize=8,  )
    def test_should_have_names( self ):
        self.assertEqual( "REPEAT", self.complex_05.name )
        self.assertEqual( "ITEM", self.complex_10.name )
    def test_should_index_complex_item( self ):
        """Partial index works from top to bottom.
        COBOL is 1-based, but that's ignored here."""
        complex= self.complex_10.index(0)
        self.assertEqual( 5, complex.offset )
        self.assertEqual( 2, complex.size )
        complex= self.complex_10.index(1)
        self.assertEqual( 13, complex.offset )
        self.assertEqual( 2, complex.size )
        complex= self.complex_10.index(2)
        self.assertEqual( 21, complex.offset )
        self.assertEqual( 2, complex.size )
        complex= self.complex_10.index(3)
        self.assertEqual( 29, complex.offset )
        self.assertEqual( 2, complex.size )
    def test_should_index_complex_group( self ):
        complex= self.complex_10.index(1,0)
        self.assertEqual( 13, complex.offset )
        self.assertEqual( 2, complex.size )
        complex= self.complex_10.index(1,1)
        self.assertEqual( 15, complex.offset )
        self.assertEqual( 2, complex.size )
        complex= self.complex_10.index(2,0)
        self.assertEqual( 21, complex.offset )
        self.assertEqual( 2, complex.size )
        complex= self.complex_10.index(2,1)
        self.assertEqual( 23, complex.offset )
        self.assertEqual( 2, complex.size )

14.7.4. DDE Data Access

The data access mediated by a DDE-as-schema is a bit more complex than the trivial (flat) data access mediated by the schema representation from the schema package.

Note that we don’t test sheet.LazyRow on workbook.Fixed_Workbook. It doesn’t work properly because the workbook.Fixed_Workbook doesn’t know about the new cobol.RepeatingAttribute that models repeating groups.

The cobol.Character_File, however, can depend on cobol.RepeatingAttribute.

class Test_Dimensional_Access( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE( level='01', name='SURVEY-RESPONSES',
            allocation=MockNonRedefine(), picture=None, offset=0, size=60, occurs=MockOccurs(1),
            variably_located= False,
            )
        self.top.top= self.top
        self.group_05 = MockDDE( level='05', name='QUESTION-NUMBER',
            allocation=MockNonRedefine(), picture=None, occurs=MockOccurs(10), offset=0, totalSize=60, size=6 )
        self.top.addChild( self.group_05 )
        self.group_10 = MockDDE( level='10', name='RESPONSE-CATEGORY',
            allocation=MockNonRedefine(), picture=None, occurs=MockOccurs(3), offset=0, totalSize=6, size=2 )
        self.group_05.addChild( self.group_10 )
        self.group_15 = MockDDE( level='15', name='ANSWER',
            allocation=MockNonRedefine(), picture="99", offset=0, totalSize=2, size=2 )
        self.group_15.dimensionality= (self.group_05, self.group_10,)
        self.group_10.addChild( self.group_15 )

        self.schema= MockSchema(
            (stingray.cobol.RepeatingAttribute( name="ANSWER", dde=weakref.ref(self.group_15),
                size=2, totalSize=60, ),
            ),
            dde= [self.top]
        )

        self.data = stingray.cobol.Character_File( name="",
            file_object= ["111213212223313233414243515253616263717273818283919293010203",],
            schema=self.schema )

    def test_should_get_cell( self ):
        """Get individual cell values."""
        # 1-based indexing in COBOL, Python, however, is zero-based.
        row= next( self.data.sheet( "" ).rows() )
        self.assertEqual( "11", row.cell( self.schema[0].index(0,0) ).to_str() )
        self.assertEqual( "21", row.cell( self.schema[0].index(1,0) ).to_str() )
        self.assertEqual( "22", row.cell( self.schema[0].index(1,1) ).to_str() )
        self.assertEqual( "23", row.cell( self.schema[0].index(1,2) ).to_str() )
        self.assertEqual( "93", row.cell( self.schema[0].index(8,2) ).to_str() )
    def test_should_get_array( self ):
        """Get tuple of values from incomplete index specification."""
        # 1-based indexing in COBOL, Python, however, is zero-based.
        row= next( self.data.sheet( "" ).rows() )
        #print( "Original and Tweaked =", self.schema[0], self.schema[0].index(1) )
        slice= row.cell( self.schema[0].index(1) )
        #print( "Offset, Slice =", self.schema[0].index(1).offset, slice )
        self.assertEqual( ('21', '22', '23'), tuple( c.to_str() for c in slice ) )

14.7.5. Workbook File Access

Character File and EBCDIC File access covers most of the bases.

Here are mocks to define a schema and the resulting Cell instances.

class MockAttribute:
    def __init__( self, **kw ):
        self.__dict__.update( kw )
    def __repr__( self ):
        return repr( self.__dict__ )

class MockTextCell:
    def __init__( self, buffer, workbook, attr ):
        self.raw, self.workbook, self.attr= buffer, workbook, attr
        self.value= workbook.text( self.raw, self.attr )

class MockDisplayCell( MockTextCell ):
    def __init__( self, buffer, workbook, attr ):
        self.raw, self.workbook, self.attr= buffer, workbook, attr
        self.value= workbook.number_display( self.raw, self.attr )

class MockComp3Cell( MockTextCell ):
    def __init__( self, buffer, workbook, attr ):
        self.raw, self.workbook, self.attr= buffer, workbook, attr
        self.value= workbook.number_comp3( self.raw, self.attr )

class MockCompCell( MockTextCell ):
    def __init__( self, buffer, workbook, attr ):
        self.raw, self.workbook, self.attr= buffer, workbook, attr
        self.value= workbook.number_comp( self.raw, self.attr )

In principle, we need to mock the sheet.ExternalSchemaSheet that gets built.

Given a schema and some data, pick apart the row of a Character File. Generally, we’ll deal with ordinary text using no particular encoding. Python can generally handle ASCII bytes or Unicode pretty simply via the “encoding” argument to the open().

class TestCharacterFile_Text( unittest.TestCase ):
    def setUp( self ):
        self.parent= MockDDE( level="01", name="PARENT", variably_located= False, )
        self.attr_word= MockAttribute(
            name="WORD", offset=0, size=3, create=MockTextCell, dimensionality=None,
            size_scale_precision=Picture("XXX",True,3,0,0,False,None))
        self.attr_num1= MockAttribute(
            name="NUMBER-1", offset=3, size=6, create=MockDisplayCell, picture="999.99", dimensionality=None,
            size_scale_precision=Picture("999.99",False,6,0,2,True,"."))
        self.attr_num2= MockAttribute(
            name="NUMBER-2", offset=9, size=5, create=MockDisplayCell, picture="S999V99", dimensionality=None,
            size_scale_precision=Picture("999V99",False,6,0,2,True,"V"))
        self.schema= MockSchema(
            ( self.attr_word, self.attr_num1, self.attr_num2
            ),
            dde= [ self.parent ],
        )
        self.data= ["ASC123.4567890XYZ",]
        self.wb= stingray.cobol.Character_File( "name", self.data, self.schema )
    def test_should_get_cells( self ):
        row= next( self.wb.sheet( "IGNORED" ).rows() )
        self.assertEqual( "ASC", row.cell( self.attr_word ).value )
        self.assertEqual( decimal.Decimal('123.45'), row.cell( self.attr_num1 ).value )
        self.assertEqual( decimal.Decimal('678.90'), row.cell( self.attr_num2 ).value )

Do we need TestCharacterFile_Bytes as a separate test case? Probably not. The cobol.Character_File is a workbook.Fixed_Workbook which is text, not bytes.

Given a schema and some data, pick apart the row of a fake EBCDIC File. This file has the default RECFM of F – no blocking and no header words.

class TestEBCDICFile_Fixed( unittest.TestCase ):
    def setUp( self ):
        self.parent= MockDDE( level="01", name="PARENT", variably_located= False, )
        self.attr_word= MockAttribute(
            name="WORD", offset=0, size=3, create=MockTextCell, dimensionality=None,
            size_scale_precision=Picture("XXX",True,3,0,0,False,None), )
        self.attr_num1= MockAttribute(
            name="NUMBER-1", offset=3, size=6, create=MockDisplayCell, picture="999.99", dimensionality=None,
            size_scale_precision=Picture("999.99",False,6,0,2,True,"."), )
        self.attr_num2= MockAttribute(
            name="NUMBER-2", offset=9, size=5, create=MockDisplayCell, picture="S999V99", dimensionality=None,
            size_scale_precision=Picture("999V99",False,5,0,2,True,"V"), )
        self.attr_comp= MockAttribute(
            name="NUMBER-3", offset=14, size=4, create=MockCompCell, picture="S99999999", dimensionality=None,
            size_scale_precision=Picture("999V99",False,5,0,2,True,"V"), )
        self.attr_comp3= MockAttribute(
            name="NUMBER-4", offset=18, size=3, create=MockComp3Cell, picture="S999V99", dimensionality=None,
            size_scale_precision=Picture("999V99",False,3,0,2,True,"V"), )
        self.schema= MockSchema(
            ( self.attr_word, self.attr_num1, self.attr_num2, self.attr_comp, self.attr_comp3
            ),
            dde= [ self.parent ],
        )
    def test_should_get_cells( self ):
        self.data= io.BytesIO(
            b"\xe9\xd6\xe2" # WORD="ZOS"
            b"\xf1\xf2\xf3K\xf4\xf5" # NUMBER-1="123.45"
            b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
            b"\x00\x00\x12\x34" # NUMBER-3=4660
            b"\x98\x76\x5d" # NUMBER-4=-987.65
        )
        self.wb= stingray.cobol.EBCDIC_File( "name", self.data, self.schema )
        row= next( self.wb.sheet( "IGNORED" ).rows() )
        self.assertEqual( 21, self.schema.lrecl() ) # last field has offset 18, size 3
        self.assertEqual( "ZOS", row.cell( self.attr_word ).value )
        self.assertEqual( decimal.Decimal('123.45'), row.cell( self.attr_num1 ).value )
        self.assertEqual( decimal.Decimal('678.90'), row.cell( self.attr_num2 ).value )
        self.assertEqual( decimal.Decimal('4660'), row.cell( self.attr_comp ).value )
        self.assertEqual( decimal.Decimal('-987.65'), row.cell( self.attr_comp3 ).value )

Given a schema and some data, pick apart the row of a fake EBCDIC File. This file has a RECFM of V, it has Record Descriptor Word (RDW)

class TestEBCDICFile_Variable( TestEBCDICFile_Fixed ):
    def test_should_get_cells( self ):
        """Data has 4 byte RDW in front of the row."""
        self.data= io.BytesIO(
            b"\x00\x19\x00\x00" # RDW
            b"\xe9\xd6\xe2" # WORD="ZOS"
            b"\xf1\xf2\xf3K\xf4\xf5" # NUMBER-1="123.45"
            b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
            b"\x00\x00\x12\x34" # NUMBER-3=4660
            b"\x98\x76\x5d" # NUMBER-4=-987.65
        )
        self.wb= stingray.cobol.EBCDIC_File( "name", self.data, self.schema, RECFM="V" )
        row= next( self.wb.sheet( "IGNORED" ).rows() )
        self.assertEqual( 21, self.schema.lrecl() ) # last field has offset 18, size 3
        self.assertEqual( "ZOS", row.cell( self.attr_word ).value )
        self.assertEqual( decimal.Decimal('123.45'), row.cell( self.attr_num1 ).value )
        self.assertEqual( decimal.Decimal('678.90'), row.cell( self.attr_num2 ).value )
        self.assertEqual( decimal.Decimal('4660'), row.cell( self.attr_comp ).value )
        self.assertEqual( decimal.Decimal('-987.65'), row.cell( self.attr_comp3 ).value )

EBCDIC File with VB format. It has Block Descriptor Word (BDW) and Record Descriptor Word (RDW).

class TestEBCDICFile_VariableBlocked( TestEBCDICFile_Fixed ):
    def test_should_get_cells( self ):
        """Data has 4 byte BDW and 4 byte RDW in front of the row."""
        # Build 2 blocks each with two records.
        self.data= io.BytesIO(
        b"\x00\x36\x00\x00" #BDW: 54= 4+sum of RDW's
            b"\x00\x19\x00\x00" # RDW: 25=4+len(data)
                b"\xe9\xd6\xe2" # WORD="ZOS"
                b"\xf1\xf2\xf3K\xf4\xf5" # NUMBER-1="123.45"
                b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
                b"\x00\x00\x12\x34" # NUMBER-3=4660
                b"\x98\x76\x5d" # NUMBER-4=-987.65
            b"\x00\x19\x00\x00" # RDW
                b"\xe9\xd6\xe2" # WORD="ZOS"
                b"\xf1\xf2\xf3K\xf4\xf6" # NUMBER-1="123.46"
                b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
                b"\x00\x00\x12\x34" # NUMBER-3=4660
                b"\x98\x76\x5d" # NUMBER-4=-987.65
        b"\x00\x36\x00\x00" #BDW
            b"\x00\x19\x00\x00" # RDW
                b"\xe9\xd6\xe2" # WORD="ZOS"
                b"\xf1\xf2\xf3K\xf4\xf7" # NUMBER-1="123.47"
                b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
                b"\x00\x00\x12\x34" # NUMBER-3=4660
                b"\x98\x76\x5d" # NUMBER-4=-987.65
            b"\x00\x19\x00\x00" # RDW
                b"\xe9\xd6\xe2" # WORD="ZOS"
                b"\xf1\xf2\xf3K\xf4\xf8" # NUMBER-1="123.48"
                b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
                b"\x00\x00\x12\x34" # NUMBER-3=4660
                b"\x98\x76\x5d" # NUMBER-4=-987.65
        )
        self.wb= stingray.cobol.EBCDIC_File( "name", self.data, self.schema, RECFM="VB" )
        row_iter= self.wb.sheet( "IGNORED" ).rows()
        row= next( row_iter )
        self.assertEqual( 21, self.schema.lrecl() ) # last field has offset 18, size 3
        self.assertEqual( "ZOS", row.cell( self.attr_word ).value )
        self.assertEqual( decimal.Decimal('123.45'), row.cell( self.attr_num1 ).value )
        self.assertEqual( decimal.Decimal('678.90'), row.cell( self.attr_num2 ).value )
        self.assertEqual( decimal.Decimal('4660'), row.cell( self.attr_comp ).value )
        self.assertEqual( decimal.Decimal('-987.65'), row.cell( self.attr_comp3 ).value )
        row2= next( row_iter )
        self.assertEqual( decimal.Decimal('123.46'), row2.cell( self.attr_num1 ).value )
        row3= next( row_iter )
        self.assertEqual( decimal.Decimal('123.47'), row3.cell( self.attr_num1 ).value )
        row4= next( row_iter )
        self.assertEqual( decimal.Decimal('123.48'), row4.cell( self.attr_num1 ).value )
        with self.assertRaises(StopIteration):
            next(row_iter)

Given a schema and some data, pick apart the row of a fake EBCDIC File. This file has a RECFM of V, but not Record Descriptor Word (RDW). The RECFM=N should be able to parse it, however.

class TestEBCDICFile_VariableWithoutRDW( TestEBCDICFile_Fixed ):
    def test_should_get_cells( self ):
        """Data lacks a 4 byte RDW in front of the row."""
        buffer= (
            b"\xe9\xd6\xe2" # WORD="ZOS"
            b"\xf1\xf2\xf3K\xf4\xf5" # NUMBER-1="123.45"
            b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
            b"\x00\x00\x12\x34" # NUMBER-3=4660
            b"\x98\x76\x5d" # NUMBER-4=-987.65

            b"\xe9\xd6\xe2" # WORD="ZOS"
            b"\xf1\xf2\xf3K\xf4\xf5" # NUMBER-1="123.45"
            b"\xf6\xf7\xf8\xf9\xf0" # NUMBER-2="678.90"
            b"\x00\x00\x12\x34" # NUMBER-3=4660
            b"\x98\x76\x5d" # NUMBER-4=-987.65
        )
        self.data= io.BytesIO( buffer )
        self.wb= stingray.cobol.EBCDIC_File( "name", self.data, self.schema, RECFM="N" )
        row_iter= self.wb.sheet( "IGNORED" ).rows()
        row= next( row_iter )
        self.assertEqual( 21, self.schema.lrecl() ) # last field has offset 18, size 3
        self.assertEqual( "ZOS", row.cell( self.attr_word ).value )
        self.assertEqual( decimal.Decimal('123.45'), row.cell( self.attr_num1 ).value )
        self.assertEqual( decimal.Decimal('678.90'), row.cell( self.attr_num2 ).value )
        self.assertEqual( decimal.Decimal('4660'), row.cell( self.attr_comp ).value )
        self.assertEqual( decimal.Decimal('-987.65'), row.cell( self.attr_comp3 ).value )
        row2= next( row_iter )
        with self.assertRaises(StopIteration):
            row3= next( row_iter )

Todo

EBCDIC File V format with Occurs Depending On to show the combination.

EBCDIC file with bad numeric data. This doesn’t use the above Mocks. It uses real Cell definitions and real Usage definitions because the real Usage definitions tolerate bad data.

class TestEBCDICFile_Invalid( unittest.TestCase ):
    def setUp( self ):
        self.parent= MockDDE( level="01", name="PARENT", variably_located= False, )
        self.display= stingray.cobol.defs.UsageDisplay("DISPLAY")
        self.display.numeric= True
        self.comp= stingray.cobol.defs.UsageComp("COMP")
        self.comp3= stingray.cobol.defs.UsageComp3("COMP-3")
        self.attr_word= MockAttribute(
            name="WORD", offset=0, size=3, create=stingray.cobol.defs.TextCell, dimensionality=None,
            size_scale_precision=Picture("XXX",True,3,0,0,False,None), )
        self.attr_num1= MockAttribute(
            name="NUMBER-1", offset=3, size=6, create=self.display.create_func, picture="999.99", dimensionality=None,
            size_scale_precision=Picture("999.99",False,6,0,2,True,"."), )
        self.attr_num2= MockAttribute(
            name="NUMBER-2", offset=9, size=5, create=self.display.create_func, picture="S999V99", dimensionality=None,
            size_scale_precision=Picture("999V99",False,6,0,2,True,"V"), )
        self.attr_comp= MockAttribute(
            name="NUMBER-3", offset=14, size=4, create=self.comp.create_func, picture="S99999999", dimensionality=None,
            size_scale_precision=Picture("999V99",False,6,0,2,True,"V"), )
        self.attr_comp3= MockAttribute(
            name="NUMBER-4", offset=18, size=5, create=self.comp3.create_func, picture="S999V99", dimensionality=None,
            size_scale_precision=Picture("999V99",False,6,0,2,True,"V"), )
        self.schema= MockSchema(
            ( self.attr_word, self.attr_num1, self.attr_num2, self.attr_comp, self.attr_comp3
            ),
            dde= [ self.parent ],
        )
    def test_should_get_error_cells( self ):
        self.data= io.BytesIO( b"\xe9\xd6\xe2\xf1\xd6\xf3K\xf4\xd6\xf6\xf7\xf8\xd6\xf0\x00\x00\x12\xd6\x00\x00\x00\x00\x00" )
        self.wb= stingray.cobol.EBCDIC_File( "name", self.data, self.schema )
        row= next( self.wb.sheet( "IGNORED" ).rows() )
        self.assertEqual( 23, self.schema.lrecl() )
        self.assertEqual( "ZOS", row.cell( self.attr_word ).value )
        self.assertIsNone( row.cell( self.attr_num1 ).value )
        self.assertEqual( b'\xf1\xd6\xf3K\xf4\xd6', row.cell( self.attr_num1 ).raw )
        self.assertIsNone( row.cell( self.attr_num2 ).value )
        self.assertEqual( b'\xf6\xf7\xf8\xd6\xf0', row.cell( self.attr_num2 ).raw )
        # Should Work: can't easily corrupt USAGE COMPUTATIONAL data.
        self.assertEqual( decimal.Decimal('4822'), row.cell( self.attr_comp ).value )
        self.assertEqual( b'\x00\x00\x12\xd6', row.cell( self.attr_comp ).raw )
        # Should this work? Not clear.
        #self.assertIsNone( row.cell( self.attr_comp3 ).value )
        self.assertEqual( decimal.Decimal('0'), row.cell( self.attr_comp3 ).value )
        self.assertEqual( b'\x00\x00\x00\x00\x00', row.cell( self.attr_comp3 ).raw )

14.7.6. Test Conversions

The various PIC and USAGE format subtleties lead to a number of conversion test cases. The attributes are a shortcut for the DDE. This includes some explicit attributes, plus the picture_parser return value. It’s a Picture instance, a tuple of (final, alpha, len(final), scale, precision, signed, decimal)

class TestNumberConversion( unittest.TestCase ):
    def setUp( self ):
        self.wb= stingray.cobol.EBCDIC_File
    def test_should_convert_display(self):
        attr1= types.SimpleNamespace(
            size_scale_precision = Picture('999v99', False, 5, 0, 2, True, "V") )
        n1= self.wb.number_display( b'\xf1\xf2\xf3\xf4\xf5', attr1 )
        self.assertEqual( decimal.Decimal('123.45'), n1 )
        attr2= types.SimpleNamespace(
            size_scale_precision = Picture('99999V', False, 5, 0, 0, True, "V") )
        n2= self.wb.number_display( b'\xf1\xf2\xf3\xf4\xf5', attr2 )
        self.assertEqual( decimal.Decimal('12345'), n2 )
        attr3= types.SimpleNamespace(
            size_scale_precision = Picture('999.99', False, 5, 0, 2, True, ".") )
        n3= self.wb.number_display( b'\xf1\xf2\xf3K\xf4\xf5', attr3 )
        self.assertEqual( decimal.Decimal('123.45'), n3 )

    def test_should_convert_display_2(self):
        attr1= types.SimpleNamespace(
            size_scale_precision = Picture('9', False, 1, 0, 0, True, "V") )
        n1= self.wb.number_display( b'\xc4', attr1 )
        self.assertEqual( decimal.Decimal('4'), n1 )
        n2= self.wb.number_display( b'\xd4', attr1 )
        self.assertEqual( decimal.Decimal('-4'), n2 )

    def test_should_convert_comp3(self):
        attr1= types.SimpleNamespace(
            size_scale_precision = Picture('999v99', False, 5, 0, 2, True, "V") )
        n1= self.wb.number_comp3( b'\x12\x34\x98\x76\x5d', attr1 )
        self.assertEqual( decimal.Decimal('-1234987.65'), n1 )
        attr2= types.SimpleNamespace(
            size_scale_precision = Picture('99999V', False, 5, 0, 0, True, "V") )
        n2= self.wb.number_comp3( b'\x12\x34\x98\x76\x5d', attr2 )
        self.assertEqual( decimal.Decimal('-123498765'), n2 )

14.7.7. Test Suite and Runner

In case we want to build up a larger test suite, we avoid doing any real work unless this is the main module being executed.

import test
suite= test.suite_maker( globals() )

if __name__ == "__main__":
    with test.Logger( stream=sys.stdout, level=logging.DEBUG ):
        logging.info( __file__ )
        unittest.TextTestRunner().run(suite())
        #unittest.main( Test_Dimensional_Access() ) # Specific debugging