14.8. COBOL Schema Loader Unit Tests

These are unit tests of various parts of the COBOL schema loader. See COBOL Loader Module – Parse COBOL Source to Load a Schema.

14.8.1. Overheads

"""stingray.cobol.loader Unit Tests."""
import unittest
import stingray.cobol.loader
import weakref
import io

14.8.2. Lexical Scanner

At a low-level, a parser requires a lexical scanner to break the input into discrete tokens. In the case of COBOL, there are mercifully few rules to follow.

A few test cases will demonstrate that line-ending . as well as a simple version of COBOL quoting rules are followed. The full COBOL language isn’t being parsed, just the part of the language used for DDE’s.

class TestLexicalScanner( unittest.TestCase ):
    def test_should_scan( self ):
        copy1= """\
      * COPY1.COB
       01  DETAIL-LINE.
           05                              PIC X(7).
           05  QUESTION                    PIC ZZ.
           05                              PIC X(6).
           05  PRINT-YES                   PIC ZZ.
           05                              PIC X(3).
         SKIP2
           05  PRINT-NO                    PIC ZZ.
           05                              PIC X(6).
           05  NOT-SURE                    PIC ZZ.
           05                              PIC X(7).
"""
        result= ['01', 'DETAIL-LINE', '.',
        '05', 'PIC', 'X(7)', '.',
        '05', 'QUESTION', 'PIC', 'ZZ', '.',
        '05', 'PIC', 'X(6)', '.',
        '05', 'PRINT-YES', 'PIC', 'ZZ', '.',
        '05', 'PIC', 'X(3)', '.',
        '05', 'PRINT-NO', 'PIC', 'ZZ', '.',
        '05', 'PIC', 'X(6)', '.',
        '05', 'NOT-SURE', 'PIC', 'ZZ', '.',
        '05', 'PIC', 'X(7)', '.']
        scanner= stingray.cobol.loader.Lexer().scan( copy1 )
        tokens = list( scanner )
        self.assertEqual( len(result), len(tokens) )
        self.assertEqual( result, tokens )

    def test_should_scan_pic( self ):
        copy2= """\
      * COPY2.COB
       01  DETAIL-LINE.
           05  FANCY-FORMAT                PIC 9(7).99.
           05  SSN-FORMAT                  PIC 999-99-9999.
"""
        result= ['01', 'DETAIL-LINE', '.',
        '05', 'FANCY-FORMAT', 'PIC', '9(7).99', '.',
        '05', 'SSN-FORMAT', 'PIC', '999-99-9999', '.']
        scanner= stingray.cobol.loader.Lexer().scan( copy2 )
        tokens = list( scanner )
        self.assertEqual( len(result), len(tokens) )
        self.assertEqual( result, tokens )

    def test_should_scan_quotes( self ):
        copy3= """\
      * COPY3.COB
       01  SIMPLE-LINE.
           05  VALUE-1                PIC X(10) VALUE 'STRING    '.
           05  VALUE-2                PIC X(10) VALUE "STRING    ".
"""
        result= [ '01', 'SIMPLE-LINE', '.',
        '05', 'VALUE-1', 'PIC', 'X(10)', 'VALUE', "'STRING    '", '.',
        '05', 'VALUE-2', 'PIC', 'X(10)', 'VALUE', '"STRING    "', '.']
        scanner= stingray.cobol.loader.Lexer().scan( copy3 )
        tokens = list( scanner )
        self.assertEqual( len(result), len(tokens) )
        self.assertEqual( result, tokens )

    def test_should_replace_text( self ):
        copy4= """\
      * COPY4.COB
       01  'XY'-SIMPLE-LINE.
           05  'XY'-VALUE-1                PIC X(10) VALUE 'STRING    '.
           05  'XY'-VALUE-2                PIC X(10) VALUE "STRING    ".
"""
        result= [ '01', 'AB-SIMPLE-LINE', '.',
        '05', 'AB-VALUE-1', 'PIC', 'X(10)', 'VALUE', "'STRING    '", '.',
        '05', 'AB-VALUE-2', 'PIC', 'X(10)', 'VALUE', '"STRING    "', '.']
        scanner= stingray.cobol.loader.Lexer(replacing=[("'XY'","AB")]).scan( copy4 )
        tokens = list( scanner )
        self.assertEqual( len(result), len(tokens) )
        self.assertEqual( result, tokens )

14.8.3. Long Lexical Scanner

Some copybooks have junk at the left and right on each line of source. A separate subclass can handle this.

class TestLongLexicalScanner( unittest.TestCase ):
    def setUp( self ):
        self.copy1= """\
      **************************************************************
       01  REPORT-TAPE-DETAIL-RECORD.
           02  RDT-REC-CODE-BYTES.                                      00000130
       EJECT
               03  RDT-REC-CODE-KEY              PIC X.                 00000140
        """
        self.scanner= stingray.cobol.loader.Lexer_Long_Lines().scan( self.copy1 )
    def test_should_scan( self ):
        result= ['01', 'REPORT-TAPE-DETAIL-RECORD', '.',
        '02', 'RDT-REC-CODE-BYTES', '.',
        '03', 'RDT-REC-CODE-KEY', 'PIC', 'X', '.',
        ]
        tokens = list( self.scanner )
        self.assertEqual( len(result), len(tokens) )
        self.assertEqual( result, tokens )

14.8.4. Picture Parsing

A picture clause has it’s own “sub-language” for describing an elementary piece of data. From the picture clause, we extract a number of features.

final
final picture with ()'s expanded.
alpha
boolean alpha = True, numeric = False.
length
len(final)
scale
count of "P" positions
precision
digits to the right of the decimal point
signed
boolean
decimal
"." or "V" or None
class TestPictureParser( unittest.TestCase ):
    def test_should_expand( self ):
        pic= stingray.cobol.loader.picture_parser( "X(7)" )
        self.assertEqual( "XXXXXXX", pic.final )
        self.assertTrue( pic.alpha )
        self.assertEqual( 7, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 0, pic.precision )
        self.assertFalse( pic.signed )
        self.assertIsNone( pic.decimal )
    def test_should_handle_z( self ):
        pic= stingray.cobol.loader.picture_parser( "ZZ" )
        self.assertEqual( "ZZ", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 2, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 0, pic.precision )
        self.assertFalse( pic.signed )
        self.assertIsNone( pic.decimal )
    def test_should_handle_9( self ):
        pic= stingray.cobol.loader.picture_parser( "999" )
        self.assertEqual( "999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 3, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 0, pic.precision )
        self.assertFalse( pic.signed )
        self.assertIsNone( pic.decimal )
    def test_should_handle_complex( self ):
        pic= stingray.cobol.loader.picture_parser( "9(5)V99" )
        self.assertEqual( "9999999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 7, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 2, pic.precision )
        self.assertFalse( pic.signed )
        self.assertEqual( "V", pic.decimal )
    def test_should_handle_signed( self ):
        pic= stingray.cobol.loader.picture_parser( "S9(7)V99" )
        self.assertEqual( "999999999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 9, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 2, pic.precision )
        self.assertTrue( pic.signed )
        self.assertEqual( "V", pic.decimal )
    def test_should_handle_db( self ):
        pic= stingray.cobol.loader.picture_parser( "DB9(5).99" )
        self.assertEqual( "DB99999.99", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 10, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 2, pic.precision )
        self.assertTrue( pic.signed )
        self.assertEqual( ".", pic.decimal )
    def test_should_handle_signed_and_v( self ):
        pic= stingray.cobol.loader.picture_parser( "S9(4)V" )
        self.assertEqual( "9999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 4, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 0, pic.precision )
        self.assertTrue( pic.signed )
        self.assertEqual( "V", pic.decimal )
    def test_should_handle_multiple_repeats( self ):
        pic= stingray.cobol.loader.picture_parser( "9(6).9(3)" )
        self.assertEqual( "999999.999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 10, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 3, pic.precision )
        self.assertFalse( pic.signed )
        self.assertEqual( ".", pic.decimal )
    def test_should_handle_trailing_repeat_1(self):
        pic= stingray.cobol.loader.picture_parser("SV9(5)")
        self.assertEqual( "99999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 5, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 5, pic.precision )
        self.assertTrue( pic.signed )
        self.assertEqual( "V", pic.decimal )
    def test_should_handle_trailing_repeat_2(self):
        pic= stingray.cobol.loader.picture_parser("SV9(05)")
        self.assertEqual( "99999", pic.final )
        self.assertFalse( pic.alpha )
        self.assertEqual( 5, pic.length )
        self.assertEqual( 0, pic.scale )
        self.assertEqual( 5, pic.precision )
        self.assertTrue( pic.signed )
        self.assertEqual( "V", pic.decimal )

14.8.5. Usage

A Usage object is attached to a DDE to explain how to decode the bytes that will be found in the record. There are many cases in COBOL, but we only really care about three: DISPLAY, COMP and COMP-3.

class TestUsageDisplay( unittest.TestCase ):
    def setUp( self ):
        self.usage = stingray.cobol.defs.UsageDisplay( "DISPLAY" )
        self.picture= stingray.cobol.loader.Picture( "99999", False, 5, 0, 2, True, "V" )
    def test_should_show_size( self ):
        self.usage.setTypeInfo( self.picture )
        self.assertEqual( 5, self.usage.size() )
        self.assertEqual( "DISPLAY", self.usage.source() )

Note the sizing issue for COMP:

Picture Info Bytes
if 1<=(int+fract)<=4 2
if 5<=(int+fract)<=9 4
if 10<=(int+fract)<=18 8
class TestUsageComp( unittest.TestCase ):
    def setUp( self ):
        self.usage = stingray.cobol.defs.UsageComp( "COMP" )
    def test_should_show_size_999( self ):
        self.picture= stingray.cobol.loader.Picture( "999", False, 3, 0, 0, True, None )
        self.usage.setTypeInfo( self.picture )
        self.assertEqual( 2, self.usage.size() )
        self.assertEqual( "COMP", self.usage.source() )
    def test_should_show_size_S9_4( self ):
        self.picture= stingray.cobol.loader.Picture( "9999", False, 4, 0, 0, True, "V" )
        self.usage.setTypeInfo( self.picture )
        self.assertEqual( 2, self.usage.size() )
        self.assertEqual( "COMP", self.usage.source() )
    def test_should_show_size_S9_5( self ):
        self.picture= stingray.cobol.loader.Picture( "99999", False, 5, 0, 0, True, "V" )
        self.usage.setTypeInfo( self.picture )
        self.assertEqual( 4, self.usage.size() )
        self.assertEqual( "COMP", self.usage.source() )
class TestUsageComp3( unittest.TestCase ):
    def setUp( self ):
        self.usage = stingray.cobol.defs.UsageComp3( "COMP-3" )
        self.picture= stingray.cobol.loader.Picture( "9999999", False, 7, 0, 2, True, "V" )
    def test_should_show_size( self ):
        self.usage.setTypeInfo( self.picture )
        self.assertEqual( 4, self.usage.size() )
        self.assertEqual( "COMP-3", self.usage.source() )

14.8.6. Allocation

There are three kinds: group (i.e., a header under a group), successor, and redefines.

A Redefines object gets the offset information from another named element. A Group object for an item. A non-redefined element is “real”: it has a proper size and offset.

An item with a REDEFINES clause is an alias for another element. The offset comes from the other element. The size is reported as zero to simplify offset calculations.

Here’s a Mock DDE which can have a redefines clause, or be referenced by a redefines clause.

class MockDDE:
    def __init__( self, **kw ):
        self.children= []
        self.top= weakref.ref(self) # default
        self.__dict__.update( kw )
    def get( self, name ):
        return [c for c in self.children if c.name == name][0]
    def addChild( self, child ):
        self.children.append( child )
        child.parent= weakref.ref(self)
        child.top= self.top

There are two non-redefinies cases: Successor and Group. The DDE stands for itself. The size is as computed. The offset is as generated by the cobol.defs.setSizeAndOffset() function.

class TestAllocation_Group( unittest.TestCase ):
    def setUp( self ):
        self.parent= MockDDE( size=123, allocation=None,
            occurs=stingray.cobol.defs.Occurs(), totalSize=123 )
        self.group = stingray.cobol.defs.Group()
        self.dde= MockDDE( size=123, allocation=self.group,
            occurs=stingray.cobol.defs.Occurs(), totalSize=123 )
        self.parent.addChild( self.dde )
    def test_should_get_size_and_offset( self ):
        self.group.resolve( self.dde )
        self.assertEqual( 123, self.dde.allocation.totalSize() )
        self.assertEqual( 13, self.dde.allocation.offset(13) )
        self.assertIs( self.dde, self.dde.allocation.dde() )

class TestAllocation_Successor( unittest.TestCase ):
    def setUp( self ):
        self.parent= MockDDE( size=12, allocation=None,
            occurs=stingray.cobol.defs.Occurs(), totalSize=12 )
        self.prev= MockDDE( size=5,
            occurs=stingray.cobol.defs.Occurs(), totalSize=5 )
        self.parent.addChild( self.prev )
        self.successor = stingray.cobol.defs.Successor( self.prev )
        self.dde= MockDDE( size=7, allocation=self.successor, occurs=stingray.cobol.defs.Occurs(), totalSize=7 )
        self.parent.addChild( self.dde )
    def test_should_get_size_and_offset( self ):
        self.successor.resolve( self.dde )
        self.assertEqual( 7, self.dde.allocation.totalSize() )
        self.assertEqual( 5, self.dde.allocation.offset(5) )
        self.assertIs( self.dde, self.dde.allocation.dde() )

Redefines is a reference to another DDE. The other DDE is located by the resolver pass. The size and offset come from the other element.

class TestAllocation_Rdefines( unittest.TestCase ):
    def setUp( self ):
        self.parent= MockDDE( name= "TOP", size=123, allocation=None, occurs=stingray.cobol.defs.Occurs(), totalSize=12 )
        self.otherdde= MockDDE( name= "SOME-NAME", size=100, offset=23 )
        self.parent.addChild( self.otherdde )

        self.redefines_some_name = stingray.cobol.defs.Redefines( "SOME-NAME" )
        self.dde= MockDDE( name= "REDEF", size=47, allocation=self.redefines_some_name, occurs=stingray.cobol.defs.Occurs() )
        self.parent.addChild( self.dde )
    def test_should_get_size_and_offset( self ):
        """Since this redefines something else, it doesn't contribute any size."""
        self.redefines_some_name.resolve( self.dde )
        self.assertEqual( 0, self.dde.allocation.totalSize() )
        self.assertEqual( 23, self.dde.allocation.offset(13) )
        self.assertIs( self.dde, self.dde.allocation.dde() )

14.8.7. Size And Offset Function

The idea is that this function does a depth-first traversal to accumulate the size in each group-level DDE.

14.8.7.1. Size and Offset Mocks

To test size and offset function, we need a number of mocks. We need to mock a DDE. Plus, we need to mock Usage and Redefines classes, also.

We also mock the iterable protocol of the DDE.

class MockDDE2:
    def __init__( self, **kw ):
        self.occurs= stingray.cobol.defs.Occurs()
        self.allocation= None
        self.children= []
        self.parent= None
        self.level= None
        self.name= "FILLER"
        self.picture= ""
        self.sizeScalePrecision= ()
        self.dimensionality= ()
        self.indent= 0
        self.__dict__.update( kw )
        self.size= self.usage.length
    def __repr__( self ):
        rc= ""
        if isinstance(self.allocation,stingray.cobol.defs.Redefines):
            rc= "REDEFINES {0}".format( self.allocation.name )
        oc= str(self.occurs)
        return "{0} {1} {2} {3} {4}.  {5} {6}".format(
            self.level, self.name, self.picture, rc, oc,
            self.offset, self.size,  )
    def addChild( self, child ):
        child.parent= weakref.ref(self)
        child.top= self.top
        self.children.append( child )
        child.indent= self.indent+1
    def get( self, name ):
        for d in self:
            if d.name == name: return d
    def pathTo( self ):
        return self.name
    def __iter__( self ):
        yield self
        for c in self.children:
            for c_i in c:
                yield c_i

class MockUsage:
    class MockCell:
        def __init__( self, buffer, workbook ):
            self.buffer= buffer
            self.workbook= workbook
    def __init__( self, source, **kw ):
        self.source_= source
        self.typeInfo= None
        self.length= 0
        self.__dict__.update( kw )
    def size( self ):
        return self.length
    def setTypeInfo( self, picture ):
        self.typeInfo= picture
        self.length= picture.length
    def source( self ):
        return self.source_
    def create_func( self, raw, workbook, attr ):
        return MockUsage.MockCell(raw, workbook, attr)


def mock_resolver( top ):
    for aDDE in top:
        if aDDE.allocation:
            aDDE.allocation.resolve( aDDE )
        else: # Not setup by test.
            #aDDE.allocation= stingray.cobol.defs.Group()
            pass

14.8.7.2. Size and Offset Cases

Exercise the size and offset function with a number of cases.

class TestFlatSizeOffset( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', allocation=stingray.cobol.defs.Group(), picture=None, usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX" ) )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="99999" ) )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=7 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="9999999" ) )
        mock_resolver( self.top )
    def test_should_set_size( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 15, self.top.size )
        self.assertEqual( 0, self.top.children[0].offset )
        self.assertEqual( 3, self.top.children[0].size )
        self.assertEqual( 3, self.top.children[1].offset )
        self.assertEqual( 5, self.top.children[1].size )
        self.assertEqual( 8, self.top.children[2].offset )
        self.assertEqual( 7, self.top.children[2].size )
class TestNestedSizeOffset( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', allocation=stingray.cobol.defs.Group(), picture=None, usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.parent= None
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX" ) )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="99999" ) )
        sub_group= MockDDE2( level='05',
            allocation=stingray.cobol.defs.Successor(self.top.children[-1]), usage=MockUsage("") )
        self.top.addChild( sub_group )
        sub_group.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=7 ),
                allocation=stingray.cobol.defs.Group(), picture="9999999" ) )
        sub_group.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=9 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="XXXXXXXXX" ) )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=11 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="XXXXXXXXXXX" ) )
        mock_resolver( self.top )
    def test_should_set_size( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 35, self.top.size )
        self.assertEqual( 0, self.top.children[0].offset )
        self.assertEqual( 3, self.top.children[0].size )
        self.assertEqual( 3, self.top.children[1].offset )
        self.assertEqual( 5, self.top.children[1].size )
        self.assertEqual( 8, self.top.children[2].offset )
        self.assertEqual( 16, self.top.children[2].size )
        self.assertEqual( 24, self.top.children[3].offset )
        self.assertEqual( 11, self.top.children[3].size )
        self.assertEqual( 8, self.top.children[2].children[0].offset )
        self.assertEqual( 7, self.top.children[2].children[0].size )
        self.assertEqual( 15, self.top.children[2].children[1].offset )
        self.assertEqual( 9, self.top.children[2].children[1].size )
class TestRedefinesSizeOffset( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', allocation=stingray.cobol.defs.Group(), usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX" ) )
        sub_group_1= MockDDE2( level='05', name="GROUP-1",
            allocation=stingray.cobol.defs.Successor(self.top.children[-1]), usage=MockUsage("") )
        self.top.addChild( sub_group_1 )
        sub_group_2= MockDDE2( level='05', name="GROUP-2",
            allocation=stingray.cobol.defs.Redefines(refers_to=sub_group_1, name="GROUP-1",), usage=MockUsage("") )
        self.top.addChild( sub_group_2 )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="XXXXX" ) )

        sub_group_1.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Group(), picture="99999" ) )
        sub_group_1.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=7 ),
                allocation=stingray.cobol.defs.Successor(sub_group_1.children[-1]), picture="9999999" ) )

        sub_group_2.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=9 ),
                allocation=stingray.cobol.defs.Group(), picture="XXXXXXXXX" ) )
        sub_group_2.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Successor(sub_group_2.children[-1]), picture="XXX" ) )

        mock_resolver( self.top )
    def test_should_set_size( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 20, self.top.size )
        self.assertEqual( 0, self.top.children[0].offset )
        self.assertEqual( 3, self.top.children[0].size )
        self.assertEqual( 3, self.top.children[1].offset )
        self.assertEqual( 12, self.top.children[1].size )
        self.assertEqual( 3, self.top.children[2].offset )
        self.assertEqual( 12, self.top.children[2].size )
        self.assertEqual( 15, self.top.children[3].offset )
        self.assertEqual( 5, self.top.children[3].size )
        self.assertEqual( 3, self.top.children[1].children[0].offset )
        self.assertEqual( 5, self.top.children[1].children[0].size )
        self.assertEqual( 8, self.top.children[1].children[1].offset )
        self.assertEqual( 7, self.top.children[1].children[1].size )
        self.assertEqual( 3, self.top.children[2].children[0].offset )
        self.assertEqual( 9, self.top.children[2].children[0].size )
        self.assertEqual( 12, self.top.children[2].children[1].offset )
        self.assertEqual( 3, self.top.children[2].children[1].size )
class TestOccursSizeOffset( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', allocation=stingray.cobol.defs.Group(), picture=None, usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX" ) )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="99999",
                occurs=stingray.cobol.defs.OccursFixed(7) ) )
        mock_resolver( self.top )
    def test_should_set_size( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 38, self.top.totalSize )
        self.assertEqual( 0, self.top.children[0].offset )
        self.assertEqual( 3, self.top.children[0].size )
        self.assertEqual( 3, self.top.children[1].offset )
        self.assertEqual( 35, self.top.children[1].totalSize )
        self.assertEqual( 5, self.top.children[1].size )
        self.assertEqual( 7, self.top.children[1].occurs.number(None) )
class TestGroupOccursSizeOffset( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', allocation=stingray.cobol.defs.Group(), picture=None, usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX" ) )
        sub_group_1= MockDDE2( level='05', name="GROUP-1",
            allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture=None,
            occurs=stingray.cobol.defs.OccursFixed(4), usage=MockUsage("") )
        self.top.addChild( sub_group_1 )
        sub_group_1.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Group(), picture="99999",
                occurs=stingray.cobol.defs.OccursFixed(7) ) )
        mock_resolver( self.top )
    def test_should_set_size( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 7*4*5+3, self.top.totalSize )
        self.assertEqual( 0, self.top.children[0].offset )
        self.assertEqual( 3, self.top.children[0].size )
        self.assertEqual( 3, self.top.children[1].offset )
        self.assertEqual( 7*4*5, self.top.children[1].totalSize )
        self.assertEqual( 7*5, self.top.children[1].size )
        self.assertEqual( 4, self.top.children[1].occurs.number(None) )
        self.assertEqual( 3, self.top.children[1].children[0].offset )
        self.assertEqual( 35, self.top.children[1].children[0].totalSize )
        self.assertEqual( 5, self.top.children[1].children[0].size )
        self.assertEqual( 7, self.top.children[1].children[0].occurs.number(None) )
    def test_should_be_repeatable( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 7*4*5+3, self.top.totalSize )
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 7*4*5+3, self.top.totalSize )
        stingray.cobol.defs.setSizeAndOffset( self.top )
        self.assertEqual( 7*4*5+3, self.top.totalSize )

14.8.8. Resolve Redefines

We need to test the cobol.loader.resolver() function.

class TestRedefinesNameResolver( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', allocation=stingray.cobol.defs.Group(), picture=None, usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX" ) )
        self.sub_group_1= MockDDE2( level='05', name="GROUP-1",
            allocation=stingray.cobol.defs.Successor(self.top.children[-1]), usage=MockUsage("") )
        self.top.addChild( self.sub_group_1 )
        self.sub_group_2= MockDDE2( level='05', name="GROUP-2",
            allocation=stingray.cobol.defs.Redefines(refers_to=None, name="GROUP-1",), usage=MockUsage("") )
        self.top.addChild( self.sub_group_2 )
        self.top.addChild(
            MockDDE2( level='05', usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="XXXXX" ) )

        self.sub_group_1.addChild(
            MockDDE2( level='10', usage=MockUsage( "",length=5 ),
                allocation=stingray.cobol.defs.Group(), picture="99999" ) )
        self.sub_group_1.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=7 ),
                allocation=stingray.cobol.defs.Successor(self.sub_group_1.children[-1]), picture="9999999" ) )

        self.sub_group_2.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=9 ),
                allocation=stingray.cobol.defs.Group(), picture="XXXXXXXXX" ) )
        self.sub_group_2.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Successor(self.sub_group_2.children[-1]), picture="XXX" ) )

        self.top.search= {
            'GROUP-1': self.sub_group_1,
            'GROUP-2': self.sub_group_2,
            }

    def test_should_resolve( self ):
        stingray.cobol.defs.resolver( self.top )
        self.assertIs( self.sub_group_1, self.sub_group_2.allocation.refers_to )

14.8.9. Set Dimensionality

The stingray.cobol.defs.setDimensionality() function pushes the OCCURS information down to each child of the OCCURS. This builds the effective dimensionality of the lowest-level elements.

* COPY3.COB
 01  SURVEY-RESPONSES.
     05  QUESTION-NUMBER         OCCURS 10 TIMES.
         10  RESPONSE-CATEGORY     OCCURS 3 TIMES.
             15  ANSWER                          PIC 99.
class Test_Dimensionality( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', name='SURVEY-RESPONSES',
            allocation=stingray.cobol.defs.Group(), picture=None, offset=0, size=60,
            occurs=stingray.cobol.defs.Occurs(), usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.top.parent= None
        self.group_05 = MockDDE2( level='05', name='QUESTION-NUMBER',
            allocation=stingray.cobol.defs.Group(), usage=MockUsage(""),
            occurs=stingray.cobol.defs.OccursFixed(10), offset=0, totalSize=60, size=6 )
        self.top.addChild( self.group_05 )
        self.group_10 = MockDDE2( level='10', name='RESPONSE-CATEGORY',
            allocation=stingray.cobol.defs.Group(), usage=MockUsage(""),
            occurs=stingray.cobol.defs.OccursFixed(3), offset=0, totalSize=6, size=2 )
        self.group_05.addChild( self.group_10 )
        self.group_15 = MockDDE2( level='15', name='ANSWER',
            allocation=stingray.cobol.defs.Group(), picture="99",
            occurs=stingray.cobol.defs.Occurs(), offset=0, totalSize=2, size=2, usage=MockUsage("99") )
        self.group_10.addChild( self.group_15 )
    def test_should_set_dimensions( self ):
        stingray.cobol.defs.setDimensionality( self.top )
        self.assertEqual( 1, self.top.occurs.number(None) )
        self.assertEqual( (), self.top.dimensionality )
        self.assertEqual( 10, self.group_05.occurs.number(None) )
        self.assertEqual( (self.group_05,), self.group_05.dimensionality )
        self.assertEqual( 3, self.group_10.occurs.number(None) )
        self.assertEqual( (self.group_05,self.group_10), self.group_10.dimensionality )
        self.assertEqual( 1, self.group_15.occurs.number(None) )
        self.assertEqual( (self.group_05,self.group_10), self.group_15.dimensionality )

14.8.10. DDE Construction Methods

DDE class has many methods. They fit into three functionality categories.

  1. Building the DDE, adding children, visiting.
  2. Getting elements by simple name. Computing path names. Getting elements by path name.
  3. Getting a run-time value from a buffer, accounting for indexes. This is DDE Access and is tested separately.

The first two areas are tested here.

class TestDDEMethods( unittest.TestCase ):
    def setUp( self ):
        self.top= stingray.cobol.defs.DDE( level='01', name='TOP', usage=MockUsage("") )
        self.top.top= weakref.ref(self.top)
        self.group= stingray.cobol.defs.DDE( level='05', name='GROUP', usage=MockUsage("")  )
        self.element= stingray.cobol.defs.DDE( level='10', name='ELEMENT', pic='X(5)', usage=MockUsage("")  )
        self.top.addChild( self.group )
        self.group.addChild( self.element )
    def test_structure( self ):
        self.assertIs( self.group, self.top.children[0] )
        self.assertIs( self.element, self.group.children[0] )
    def test_path_name( self ):
        self.assertEqual( "TOP.GROUP.ELEMENT", self.element.pathTo() )
        self.assertIs( self.element, self.top.getPath("TOP.GROUP.ELEMENT") )
    def test_get( self ):
        g= self.top.get( "GROUP" )
        self.assertIs( self.group, g )
        e= g.get( "ELEMENT" )
        self.assertIs( self.element, e )

14.8.11. Parsing Single DDE

The parser handles 11 different clauses. Additionally, it makes a single DDE element as well as making a composite DDE record. A parser depends on a lexical scanner. However, since our scanner is essentially an iterator, we don’t need to do very much to mock it.

To test just the cobol.loader.RecordFactory in isolation, we need to provide mocks for a large number of dependenices.

self.redefines_class= Redefines
self.non_redefines_class= NonRedefines
self.display_class= UsageDisplay
self.comp_class= UsageComp
self.comp3_class= UsageComp3

Note that there are numerous syntax errors which we do not test for. Ideally, a DDE clause is used in working software, and passes through a COBOL compiler. This isn’t lint for COBOL.

We have three test cases: things the parser finds (and keeps), things the parser skips gracefully, and things we can’t cope with.

class TestParser( unittest.TestCase ):
    def setUp( self ):
        self.parser= stingray.cobol.loader.RecordFactory()
        #self.parser.redefines_class= MockRedefines
        #self.parser.non_redefines_class= MockNonRedefine
        self.parser.display_class= MockUsage
        self.parser.comp_class= MockUsage
        self.parser.comp3_class= MockUsage
    def test_should_parse_group( self ):
        source= ( "01", "GROUP", "." )
        dde= next(self.parser.dde_iter(iter(source)))
        self.assertEqual( '01', dde.level )
        self.assertEqual( 'GROUP', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertIsNone( dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
    def test_should_parse_filler( self ):
        source= ( "05", "PIC", "X(10)", "." )
        dde= next(self.parser.dde_iter(iter(source)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'FILLER', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(10)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
    def test_should_parse_name( self ):
        source= ( "05", "ELEMENTARY", "PIC", "X(10)", "." )
        dde= next(self.parser.dde_iter(iter(source)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'ELEMENTARY', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(10)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
    def test_should_parse_occurs( self ):
        src1= ( "05", "ELEMENTARY-OCCURS", "PIC", "S9999", "OCCURS", "5", "TIMES", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'ELEMENTARY-OCCURS', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 5, dde.occurs.number(None) )
        self.assertEqual( "S9999", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
        src2= ( "05", "GROUP-OCCURS", "OCCURS", "7", "TIMES", "INDEXED", "BY", "IRRELEVANT", "." )
        dde= next(self.parser.dde_iter(iter(src2)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'GROUP-OCCURS', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 7, dde.occurs.number(None) )
        self.assertIsNone( dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
    def test_should_parse_picture( self ):
        """Details of picture clause tested separately."""
        src1= ( "05", "ELEMENTARY-PIC", "PIC", "S9999.999", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'ELEMENTARY-PIC', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "S9999.999", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
    def test_should_parse_redefines( self ):
        src1= ( "05", "ELEMENTARY-REDEF", "PIC", "S9999", "REDEFINES", "SOME-NAME", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'ELEMENTARY-REDEF', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "S9999", dde.picture )
        self.assertEqual( "SOME-NAME", dde.allocation.name )
        self.assertEqual( "", dde.usage.source() )
        src2= ( "05", "GROUP-REDEF", "OCCURS", "7", "TIMES", "REDEFINES", "ANOTHER-NAME", "." )
        dde= next(self.parser.dde_iter(iter(src2)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'GROUP-REDEF', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 7, dde.occurs.number(None) )
        self.assertIsNone( dde.picture )
        self.assertEqual( "ANOTHER-NAME", dde.allocation.name )
        self.assertEqual( "", dde.usage.source() )
    def test_should_parse_usage( self ):
        src1= ( "05", "USE-DISPLAY", "PIC", "S9999", "USAGE", "DISPLAY", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'USE-DISPLAY', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "S9999", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "DISPLAY", dde.usage.source() )
        src2= ( "05", "USE-COMP-3", "PIC", "S9(7)V99", "USAGE", "COMP-3", "." )
        dde= next(self.parser.dde_iter(iter(src2)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'USE-COMP-3', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "S9(7)V99", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "COMP-3", dde.usage.source() )
        src3= ( "05", "USE-COMP-3-ALT", "PIC", "S9(9)V99", "COMP-3", "." )
        dde= next(self.parser.dde_iter(iter(src3)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'USE-COMP-3-ALT', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "S9(9)V99", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "COMP-3", dde.usage.source() )
    def test_should_parse_depending_on_1( self ):
        src1= ( "05", "DEP-1", "OCCURS", "1", "TO", "5", "TIMES", "DEPENDING", "ON", "ODO-1", "." )
        self.parser.lex= iter(src1)
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'DEP-1', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( "ODO-1", dde.occurs.name ) # number(None) )
        self.assertEqual( None, dde.picture )
        self.assertIsNone( dde.allocation )
    def test_should_parse_depending_on_2( self ):
        src1= ( "05", "DEP-2", "OCCURS", "TO", "5", "TIMES", "DEPENDING", "ON", "ODO-2", "." )
        self.parser.lex= iter(src1)
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'DEP-2', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( "ODO-2", dde.occurs.name ) # number(None) )
        self.assertEqual( None, dde.picture )
        self.assertIsNone( dde.allocation )

Syntax which is silently skipped.

class TestParserSkip( unittest.TestCase ):
    def setUp( self ):
        self.parser= stingray.cobol.loader.RecordFactory()
    def test_should_skip_blank( self ):
        src1= ( "05", "BLANK-ZERO-1", "PIC", "X(10)", "BLANK", "ZERO", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'BLANK-ZERO-1', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(10)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
        self.assertIsNone( dde.initValue )
        src2= ( "05", "BLANK-ZERO-2", "PIC", "X(10)", "BLANK", "WHEN", "ZEROES", "." )
        dde= next(self.parser.dde_iter(iter(src2)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'BLANK-ZERO-2', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(10)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
        self.assertIsNone( dde.initValue )
    def test_should_skip_justified( self ):
        src1= ( "05", "JUST-RIGHT-1", "PIC", "X(10)", "JUST", "RIGHT", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'JUST-RIGHT-1', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(10)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
        self.assertIsNone( dde.initValue )
        src2= ( "05", "JUST-RIGHT-2", "PIC", "X(10)", "JUSTIFIED", "RIGHT", "." )
        dde= next(self.parser.dde_iter(iter(src2)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'JUST-RIGHT-2', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(10)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
        self.assertIsNone( dde.initValue )
    def test_should_skip_value( self ):
        src1= ( "05", "VALUE-1", "PIC", "X(8)", "VALUE", "'10 CHARS'", "." )
        dde= next(self.parser.dde_iter(iter(src1)))
        self.assertEqual( '05', dde.level )
        self.assertEqual( 'VALUE-1', dde.name )
        self.assertEqual( 0, len(dde.children) )
        self.assertEqual( 1, dde.occurs.number(None) )
        self.assertEqual( "X(8)", dde.picture )
        self.assertIsNone( dde.allocation )
        self.assertEqual( "", dde.usage.source() )
        self.assertIsNone( dde.initValue )

Todo

Test EXTERNAL, GLOBAL as Skipped Words, too.

A few clauses may be relevant for some kinds of DDE’s. These can impact the encoding of the bytes. We don’t parse them, however, because they are rarely seen in the wild.

class TestParserException( unittest.TestCase ):
    def setUp( self ):
        self.parser= stingray.cobol.loader.RecordFactory()
    def test_should_fail_renames( self ):
        src1= ( "66", "BLANK-ZERO-1", "RENAMES", "SOME-NAME", "." )
        self.assertWarns( UserWarning, next, self.parser.dde_iter(iter(src1)) )
        # Alternative RENAMES
        # self.assertRaises( stingray.cobol.defs.UnsupportedError, ...
    def test_should_fail_sign( self ):
        src1= ( "05", "SIGN-1", "PIC", "X(10)", "LEADING", "SIGN", "." )
        self.parser.lex= iter(src1)
        self.assertRaises( stingray.cobol.defs.UnsupportedError, next, self.parser.dde_iter(iter(src1)) )
        src2= ( "05", "SIGN-2", "PIC", "X(10)", "TRAILING", "." )
        self.parser.lex= iter(src2)
        self.assertRaises( stingray.cobol.defs.UnsupportedError, next, self.parser.dde_iter(iter(src2)) )
        src3= ( "05", "SIGN-3", "PIC", "X(10)", "SIGN", "IS", "SEPARATE", "." )
        self.parser.lex= iter(src3)
        self.assertRaises( stingray.cobol.defs.UnsupportedError, next, self.parser.dde_iter(iter(src3)) )
    def test_should_fail_synchronized( self ):
        src1= ( "05", "SYNC-1", "PIC", "X(10)", "SYNCHRONIZED", "." )
        self.parser.lex= iter(src1)
        self.assertRaises( stingray.cobol.defs.UnsupportedError, next, self.parser.dde_iter(iter(src1)) )

14.8.12. Parsing Complete DDE

Parsing a complete DDE is a multi-step dance. First, parse all the clauses. Then apply some standard functions to resolve the REDEFINES clauses as well as compute sizes and offsets.

To test just the cobol.loader.RecordFactory in isolation, we need to provide mocks for a large number of dependenices.

# Built during the parsing
self.redefines_class= Redefines
self.non_redefines_class= NonRedefines
self.display_class= UsageDisplay
self.comp_class= UsageComp
self.comp3_class= UsageComp3

We use dependency injection here to tease apart Redefines and Usage classes. We can use existing mocks for this.

Here’s a test of the “main” method for parsing a record. Note that stingray.cobol.loader.RecordFactory.makeRecord() is iterable, so we make a list and take the first element.

We could as easily next() it to get the first element.

Test the parser

class TestCompleteParser( unittest.TestCase ):
    def setUp( self ):
        self.parser= stingray.cobol.loader.RecordFactory()
        self.parser.display_class= MockUsage
        self.parser.comp_class= MockUsage
        self.parser.comp3_class= MockUsage
    def test_should_parse( self ):
        copy1= """
              * COPY1.COB
               01  DETAIL-LINE.
                   05                              PIC X(7).
                   05  QUESTION                    PIC ZZ.
                   05                              PIC X(6).
                   05  PRINT-YES                   PIC ZZ.
        """
        self.lex= ['01', 'DETAIL-LINE', '.',
        '05', 'PIC', 'X(7)', '.',
        '05', 'QUESTION', 'PIC', 'ZZ', '.',
        '05', 'PIC', 'X(6)', '.',
        '05', 'PRINT-YES', 'PIC', 'ZZ', '.']
        dde= list(self.parser.makeRecord( iter( self.lex ) ))[0]
        self.assertEqual( "01", dde.top().level )
        self.assertEqual( "DETAIL-LINE", dde.top().name )
        self.assertEqual( 4, len(dde.top().children) )
        self.assertEqual( "", dde.top().usage.source() )
        c0= dde.top().children[0]
        self.assertEqual( "05", c0.level )
        self.assertEqual( "FILLER", c0.name )
        self.assertEqual( "X(7)", c0.picture )
        self.assertEqual( "", c0.usage.source() )
        self.assertEqual( 1, c0.occurs.number(None) )
        c1= dde.top().children[1]
        self.assertEqual( "05", c1.level )
        self.assertEqual( "QUESTION", c1.name )
        self.assertEqual( "ZZ", c1.picture )
        self.assertEqual( "", c1.usage.source() )
        self.assertEqual( 1, c1.occurs.number(None) )
        c2= dde.top().children[2]
        self.assertEqual( "05", c2.level )
        self.assertEqual( "FILLER", c2.name )
        self.assertEqual( "X(6)", c2.picture )
        self.assertEqual( "", c2.usage.source() )
        self.assertEqual( 1, c2.occurs.number(None) )
        c3= dde.top().children[3]
        self.assertEqual( "05", c3.level )
        self.assertEqual( "PRINT-YES", c3.name )
        self.assertEqual( "ZZ", c3.picture )
        self.assertEqual( "", c3.usage.source() )
        self.assertEqual( 1, c3.occurs.number(None) )
        self.assertEqual( 5, len(list(dde) ) )

14.8.13. Schema Maker

A DDE is tranformed into a flat schema from the highly-nested DDE structure via the stingray.cobol.loader.make_schema() function.

class TestNestedSchemaMaker( unittest.TestCase ):
    def setUp( self ):
        self.top= MockDDE2( level='01', name="TOP",
            allocation=stingray.cobol.defs.Group(), picture=None, size=35, offset=0, usage=MockUsage( "" ) )
        self.top.top= weakref.ref(self.top)
        self.top.addChild(
            MockDDE2( level='05', name="FIRST", usage=MockUsage( "", length=3 ),
                allocation=stingray.cobol.defs.Group(), picture="XXX", size=3, offset=0 ) )
        self.top.addChild(
            MockDDE2( level='05', name="SECOND", usage=MockUsage( "", length=5 ),
                allocation=stingray.cobol.defs.Successor(self.top.children[-1]), picture="99999", size=5, offset=3 ) )
        sub_group= MockDDE2( level='05', name="GROUP",
            allocation=stingray.cobol.defs.Group(), picture=None, size=16, offset=8, usage=MockUsage( "" ) )
        self.top.addChild( sub_group )
        sub_group.addChild(
            MockDDE2( level='10', name="INNER", usage=MockUsage( "", length=7 ),
                allocation=stingray.cobol.defs.Group(), picture="9999999", size=8, offset=7 ) )
        sub_group.addChild(
            MockDDE2( level='10', usage=MockUsage( "", length=9 ),
                allocation=stingray.cobol.defs.Successor(sub_group.children[-1]), picture="XXXXXXXXX", size=9, offset=15) )
        self.top.addChild(
            MockDDE2( level='05', name="LAST", usage=MockUsage( "", length=11 ),
                allocation=stingray.cobol.defs.Successor(sub_group.children[-1]), picture="XXXXXXXXXXX", size=11, offset=24 ) )
    def test_should_set_size( self ):
        stingray.cobol.defs.setSizeAndOffset( self.top )
        schema = stingray.cobol.loader.make_schema( [self.top] )
        #DEBUG# print( schema )
        self.assertEqual( "TOP", schema[0].name )
        self.assertEqual( 0, schema[0].position )
        self.assertEqual( 0, schema[0].offset )
        self.assertEqual( 35, schema[0].size )
        self.assertEqual( "FIRST", schema[1].name )
        self.assertEqual( 1, schema[1].position )
        self.assertEqual( 0, schema[1].offset )
        self.assertEqual( 3, schema[1].size )
        self.assertEqual( "SECOND", schema[2].name )
        self.assertEqual( 2, schema[2].position )
        self.assertEqual( 3, schema[2].offset )
        self.assertEqual( 5, schema[2].size )
        self.assertEqual( "LAST", schema[6].name )
        self.assertEqual( 6, schema[6].position )
        self.assertEqual( 24, schema[6].offset )
        self.assertEqual( 11, schema[6].size )

14.8.14. Another Variation on Loading DDE’s

Here’s a class which does some loading. The processing is a bit convoluted, but this is a possible use case, and must work.

class SchemaLoaderClass (stingray.cobol.loader.COBOLSchemaLoader):
    lexer_class= stingray.cobol.loader.Lexer_Long_Lines

    def load( self ):
        fixed_dde_list= list()
        unused_dde_list= list()
        dde_list=list(self.parser.makeRecord( self.lexer.scan(self.source) ))
        hdr_trl_record_copybook="""\
000010 01  HEADER-TRAILER-RECORD.
000020            05  HDR-REC-TYPE                        PIC X(6).
000030            05  FILLER                              PIC X(5).
000040            05  HDR-TRL                             PIC X(8).
000050            05  FILLER                              PIC X(1).
000060            05  HDR-DATE1                           PIC X(10).
000070            05  FILLER                              PIC X(1).
000080            05  HDR-JULIAN-DATE1                    PIC X(7).
000090            05  FILLER                              PIC X(1).
000100            05  HDR-SOME-NUMBER1                    PIC X(8).
000110            05  FILLER                              PIC X(1).
000120            05  HDR-DATE2                           PIC X(10).
000130            05  FILLER                              PIC X(1).
000140            05  HDR-SOME-NUMBER2                    PIC X(7).
000150            05  FILLER                              PIC X(1).
000140            05  HDR-SOME-NUMBER3                    PIC X(4).
000160            05  HDR-REST-OF-LINE            PIC X(30).
"""
        dde_list+=list(self.parser.makeRecord( self.lexer.scan(hdr_trl_record_copybook) ))
        for dde in dde_list:
            if dde.name =='HEADER-TRAILER-RECORD':
                fixed_dde_list+= [dde]
            elif dde.name == 'ANOTHER-RECORD':
                base_dde= [dde]
            else:
                unused_dde_list+= [dde]
        schema= stingray.cobol.loader.make_schema( base_dde )
        fixed_schema= stingray.cobol.loader.make_schema( fixed_dde_list )
        return schema, fixed_schema

Confirm that this class really works

class TestSchemaLoaderClass(unittest.TestCase):
    def test_should_load(self):
        another_record ="""\
      01  ANOTHER-RECORD.
          05  FILLER PIC X(10).
        """
        example = io.StringIO(another_record)
        slc= SchemaLoaderClass(example)
        schema, fixed_schema= slc.load()
        self.assertTrue( any( f.name == "ANOTHER-RECORD" for f in schema ) )
        self.assertTrue( any( f.name == "HEADER-TRAILER-RECORD" for f in fixed_schema ) )

14.8.15. 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__":
    print( __file__ )
    unittest.TextTestRunner(verbosity=1).run(suite())