'''
### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ###
                   308-304 - Object-Oriented Software Design
                                 ASSIGNMENT  1

   TestSD.py ---
      Testing unit for both SpreadsheetData implementations.
      Must be called through TestSDlist or TestSDdict.

   last modified: 01/24/02
                       ===============================
                             Copyright (c)  2002
                            Jean-Sébastien BOLDUC
                             (jseb@cs.mcgill.ca)

                        McGill University  (Montréal)
                       ===============================

### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ###
'''

# Testing other implementations from the command line:
from sys import argv
if len(argv) > 1:
  exec('from ' + argv[-1] + ' import *')
  del argv[-1] # Don't pass this argument to unittest
else:
  # Terminate execution if not called properly:
  from sys import exit
  try:
    assert(SpreadsheetData)
  except NameError:
    print "\n*** Use either TestSDlist or TestSDdict! ***\n"
    exit()

import unittest

# List of some valid CellCoordinate objects
goodCoord = []
for i in range(1, 20):
  for j in range(1, 20):
    goodCoord = goodCoord + [CellCoordinate(i, j)]

# List of some valid CellData objects
goodData = []
for i in range(-20, 20):
  goodData = goodData + [CellData(i)]

# Lists of some invalid coordinates and datas
badCoord = [None, 'a', 1.5, [1, 2], goodData[0]]
badData  = [None, 'a', 1.5, [1, 2], goodCoord[0]]


class SpreadsheetConstructor(unittest.TestCase):

  def testGoodInitialization(self): # test for success
    '''SpreadsheetData __initi__ should succeed'''
    self.assert_(SpreadsheetData())


class SpreadsheetItemOps(unittest.TestCase):

  X = SpreadsheetData()

  def testGoodSetting(self): # test for success
    '''__setitem__ should succeed with valid coordinate and data'''
    for data in goodData:
      for coord in goodCoord:
        self.assertEqual(self.X.__setitem__(coord, data), None)

  def testBadSetting1(self): # test for failure
    '''__setitem__ should raise a KeyError on bad coordinate'''
    for coord in badCoord:
      for data in goodData + badData:
        self.assertRaises(KeyError, self.X.__setitem__, coord, data)
    # NOTE that if both the coordinate and the data are bad, the KeyError has
    # priority over the TypeError. This is consistent with the syntax:
    #    X[coord] = data

  def testBadSetting2(self): # test for failure
    '''__setitem__ should raise a TypeError on bad data'''
    for coord in goodCoord:
      for data in badData:
        self.assertRaises(TypeError, self.X.__setitem__, coord, data)
    # see NOTE in method "testBadSetting1".

  def testSanity1(self): # test for sanity
    '''__getitem__ should return value previously set by __setitem__'''
    # Fill the spreadsheet...
    m = min(len(goodData), len(goodCoord))
    for i in range(m):
      self.X[goodCoord[i]] = goodData[i]
    # ...and test
    for i in range(m):
      # test twice, to make sure the data is not corrupted
      # by the __getitem__ method:
      self.assertEqual(self.X.__getitem__(goodCoord[i]), goodData[i])
      self.assertEqual(self.X.__getitem__(goodCoord[i]), goodData[i])
      # Note that at this point, the same CellData object might be stored in
      # different coordinates. Might at some point decide to (deep)copy the
      # CellData objects prior to storing them in a spreadsheetData object: in
      # that eventuality, this test would fail unless we override
      # CellData.__cmp__ to make comparisons possible. Behaviour to determine.

  def testSanity2(self): # test for sanity
    '''stored :CellData should not be corrupted by failed __setitem__'s'''
    self.X[goodCoord[0]] = goodData[0] # Fill a cell
    for data in badData:
     try:
       self.X[goodCoord[0]] = data
     except TypeError:
       pass
     self.assertEqual(self.X.__getitem__(goodCoord[0]), goodData[0])
     # Also see not in "testSanity1"

  def testGetBadCoord(self): # test for failure
    '''__getitem__ should raise a KeyError on bad coordinate'''
    for coord in badCoord:
      self.assertRaises(KeyError, self.X.__getitem__, coord)

  def testGetEmptyCell(self): # test for success
    '''__getitem__ should return None for an empty cell'''
    self.X = SpreadsheetData() # reset SpreadsheetData object...
    # ...and fill two cells
    self.X[CellCoordinate(1, 1)] = goodData[0]
    self.X[CellCoordinate(10, 10)] = goodData[0]
    # Test "within" spreadsheet...
    self.assertEqual(self.X.__getitem__(CellCoordinate(5, 5)), None)
    # ...and test "outside" spreadsheet...
    self.assertEqual(self.X.__getitem__(CellCoordinate(100, 100)), None)

  def testDelGoodCoord(self): # test for success
    '''__delitem__ should succeed on valid coordinate'''
    self.X = SpreadsheetData() # reset SpreadsheetData object...
    # deletion should work on empty spreadsheet:
    self.assertEqual(self.X.__delitem__(CellCoordinate(1,1)), None)
    # ...and fill two cells
    self.X[CellCoordinate(1, 1)] = goodData[0]
    self.X[CellCoordinate(10, 10)] = goodData[0]
    # delete a nonempty item:
    self.assertEqual(self.X.__delitem__(CellCoordinate(1,1)), None)
    # delete an empty item:
    self.assertEqual(self.X.__delitem__(CellCoordinate(5,5)), None)
    # delete an item "outside" spreadsheet:
    self.assertEqual(self.X.__delitem__(CellCoordinate(100,100)), None)

  def testDelBadCoord(self): # test for failure
    '''__delitem__ should raise a KeyError on bad coordinate'''
    for coord in badCoord:
      self.assertRaises(KeyError, self.X.__delitem__, coord)


class SpreadsheetLUandRB(unittest.TestCase):

  def testRBandLUnonempty(self): # test for success
    '''getLU and getRB should return correct result on nonempty Spreadsheet'''
    self.X = SpreadsheetData() # create empty SpreadsheetData object...
    # partially fill spreadsheet: North, South, East and West (not the corners)
    self.X[CellCoordinate(5,  10)] = goodData[0] # North
    self.X[CellCoordinate(15, 10)] = goodData[0] # South
    self.X[CellCoordinate(10, 15)] = goodData[0] # East
    self.X[CellCoordinate(10,  5)] = goodData[0] # West
    # make sure the returned values are CellCoordinate objects:
    self.assert_(isinstance(self.X.getLU(), CellCoordinate))
    self.assert_(isinstance(self.X.getRB(), CellCoordinate))
    LU = self.X.getLU()
    RB = self.X.getRB()
    # Note: to use following lines, we need to override
    # "CellCoordinate.__cmp__" to make comparisons possible.
    ##self.assertEqual(self.X.getLU(), CellCoordinate(5,   5))
    ##self.assertEqual(self.X.getRB(), CellCoordinate(15, 15))
    # instead, we use the following:
    self.assertEqual(LU.getRow(), 5)
    self.assertEqual(LU.getColumn(), 5)
    self.assertEqual(RB.getRow(), 15)
    self.assertEqual(RB.getColumn(), 15)
    # Repeat the same test, after deleting the East and South cells:
    del self.X[CellCoordinate(10, 15)]
    del self.X[CellCoordinate(15, 10)]
    LU = self.X.getLU()
    RB = self.X.getRB()
    self.assertEqual(LU.getRow(), 5)
    self.assertEqual(LU.getColumn(), 5)
    self.assertEqual(RB.getRow(), 10)
    self.assertEqual(RB.getColumn(), 10)
    # Result should not be affected when trying to insert a corrupted data in
    # the spreadsheet:
    try:
      self.X[CellCoordinate(100, 100)] = badData[0]
    except TypeError:
      pass
    LU = self.X.getLU()
    RB = self.X.getRB()
    self.assertEqual(LU.getRow(), 5)
    self.assertEqual(LU.getColumn(), 5)
    self.assertEqual(RB.getRow(), 10)
    self.assertEqual(RB.getColumn(), 10)

  def testRBandLUempty(self): # test for success
    '''getLU and getRB should return None on empty Spreadsheet'''
    self.X = SpreadsheetData() # create empty SpreadsheetData object...
    self.assertEqual(self.X.getLU(), None)
    self.assertEqual(self.X.getRB(), None)
    # Should get same behaviour after filling and emptying a spreadsheet:
    self.X[goodCoord[0]] = goodData[0] # filling...
    del self.X[goodCoord[0]] # ...emptying
    self.assertEqual(self.X.getLU(), None)
    self.assertEqual(self.X.getRB(), None)

# Not necessary to test "SpreadsheetData.__str__". Set flag to 1 if decide to
# activate testing code.
if 0:
  class SpreadsheetSTR(unittest.TestCase):
  
    X = SpreadsheetData()

    def testStringRepres(self): # test for success
      '''TO BE COMPLETED (eventually)'''
      pass


### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ###

if __name__ == "__main__":
  unittest.main()