home *** CD-ROM | disk | FTP | other *** search
/ MacAddict 108 / MacAddict108.iso / Software / Internet & Communication / JunkMatcher 1.5.5.dmg / JunkMatcher.app / Contents / Resources / Engine / Tests.py < prev    next >
Encoding:
Python Source  |  2005-06-01  |  17.4 KB  |  436 lines

  1. #
  2. #  Tests.py
  3. #  JunkMatcher
  4. #
  5. #  Created by Benjamin Han on 2/1/05.
  6. #  Copyright (c) 2005 Benjamin Han. All rights reserved.
  7. #
  8.  
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13.  
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. # GNU General Public License for more details.
  18.  
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  22.  
  23. #!/usr/bin/env python
  24.  
  25. # IMPORTANT: Properties, Patterns and Tests can have only a single instance!
  26.  
  27. import string
  28.  
  29. from consts import *
  30. from Property import *
  31. from Pattern import *
  32. from Message import *
  33.  
  34. _locals = locals()
  35.  
  36.  
  37. class Properties (dict):
  38.     """A dictionary of properties
  39.        --------------------------
  40.        fn: file name
  41.  
  42.        Note: key is class names (string), value is instances of Property.
  43.        """
  44.     def __init__ (self, propertiesFN):
  45.         self.fn = propertiesFN
  46.         self.load()
  47.  
  48.     def load (self):
  49.         self.clear()
  50.         mode = 0
  51.         for l in filter(lambda l:len(l) and l[0] != '#',  map(string.strip, openFile(self.fn))):
  52.             if mode == 0:
  53.                 mode = 1
  54.                 recipientPattern = l[1:-1]
  55.  
  56.             else:
  57.                 mode = 0
  58.                 l = l.split(' ')
  59.                 testRecord = TestRecord(float(l[1]),
  60.                                         int(l[2]), int(l[3]), int(l[4]), int(l[5]))
  61.                 classID = l[0]
  62.                 
  63.                 if classID == 'PropertyDateInFuture':
  64.                     r = PropertyDateInFuture(testRecord, recipientPattern, int(l[6]))
  65.                 elif classID == 'PropertyDateInThePast':
  66.                     r = PropertyDateInThePast(testRecord, recipientPattern, int(l[6]))
  67.                 elif classID == 'PropertyRecipientsMismatch':
  68.                     r = PropertyRecipientsMismatch(testRecord, recipientPattern)
  69.                 elif classID == 'PropertyOpenRelay':
  70.                     r = PropertyOpenRelay(testRecord, recipientPattern, float(l[6]), int(l[7]), l[8:])
  71.                 elif classID == 'PropertyPhishingURL':
  72.                     r = PropertyPhishingURL(testRecord, recipientPattern, bool(int(l[6])))
  73.                 elif len(l) > 6:
  74.                     r = _locals[classID](testRecord, recipientPattern, int(l[6]))
  75.                 else:
  76.                     r = _locals[classID](testRecord, recipientPattern)
  77.  
  78.                 self[classID] = r
  79.  
  80.     def writeToFile (self):
  81.         strList = []
  82.         for classID, r in self.items():
  83.             testRecord = r.testRecord
  84.  
  85.             if r.recipientPattern: recipientPattern = r.recipientPattern.pattern
  86.             else: recipientPattern = ''
  87.  
  88.             s = '"%s"\n%s %s' % (recipientPattern, classID, testRecord)
  89.             if classID == 'PropertyDateInFuture':
  90.                 strList.append('%s %d\n' % (s, r.timeDelta.seconds / 60))
  91.             elif classID == 'PropertyDateInThePast':
  92.                 strList.append('%s %d\n' % (s, -r.timeDelta.days))
  93.             elif classID == 'PropertyOpenRelay':
  94.                 strList.append('%s %f %d %s\n' % (s, r.timeout, r.numLastIPs, ' '.join(r.blackLists)))
  95.             elif classID == 'PropertyPhishingURL':
  96.                 strList.append('%s %d\n' % (s, int(r.checkWhitelistedEmail)))
  97.             elif hasattr(r, 'intArg'):
  98.                 strList.append('%s %d\n' % (s, r.intArg))
  99.             else:
  100.                 strList.append('%s\n' % s)
  101.  
  102.         openFile(self.fn, 'w').write('\n'.join(strList))
  103.  
  104.     def getStatsString (self):
  105.         """Returns a string containing the statistics of all properties. The format:
  106.         'name1 cpuTime1 numTruePositive1 numTrueNegative1 numFalsePositive1 numFalseNegative1\n
  107.         name2 cpuTime2 ...'."""
  108.         return '\n'.join(['%s %s' % (name, ' '.join(map(str, r.testRecord.readAll())))
  109.                           for name, r in self.items()])
  110.  
  111.     def setStats (self, statsString):
  112.         """Update the statistics of the properties. The statsString is produced from
  113.         getStatsString()."""
  114.         if len(statsString) == 0: return
  115.         for l in map(lambda i: i.split(' '), statsString.split('\n')):
  116.             self[l[0]].testRecord.setAll(float(l[1]), int(l[2]), int(l[3]), int(l[4]), int(l[5]))
  117.  
  118.     
  119. class Patterns (dict):
  120.     """A dictionary of patterns
  121.        --------------------------
  122.        fn: file name
  123.  
  124.        Note: key is the pattern itself (Unicode string), value is instances of Pattern.
  125.        """
  126.     def __init__ (self, patternsFN):
  127.         self.fn = patternsFN
  128.         self.load()
  129.  
  130.     def load (self):
  131.         self.clear()
  132.         mode = 0
  133.         for l in filter(lambda l:len(l) and l[0] != '#',  map(string.strip, openFile(self.fn))):
  134.             if mode > 5 and l[0] == '"':
  135.                 self[pattern] = Pattern(patternName, testRecords, pattern, isManaged,
  136.                                         recipientPattern, encodingPattern)
  137.                 mode = 0
  138.  
  139.             if mode == 0:
  140.                 pattern = l[1:-1]
  141.             elif mode == 1:
  142.                 patternName = l[1:-1]
  143.             elif mode == 2:
  144.                 isManaged = (l[0] == 'M')
  145.             elif mode == 3:
  146.                 recipientPattern = l[1:-1]
  147.             elif mode == 4:
  148.                 encodingPattern = l[1:-1]
  149.             else:
  150.                 if mode == 5:
  151.                     testRecords = {}
  152.                     
  153.                 l = l.split(' ')
  154.                 testRecords[l[0]] = TestRecord(float(l[1]),
  155.                                                int(l[2]), int(l[3]), int(l[4]), int(l[5]))
  156.  
  157.             mode += 1
  158.  
  159.         # don't forget the last one
  160.         self[pattern] = Pattern(patternName, testRecords, pattern, isManaged,
  161.                                 recipientPattern, encodingPattern)
  162.  
  163.     def writeToFile (self):
  164.         """So we can print all patterns into a file."""
  165.         strList = []
  166.         for patternID, p in self.items():
  167.             if p.isManaged: stateStr = 'M'
  168.             else: stateStr = 'U'
  169.             strList.append('"%s"\n"%s"\n%s' %
  170.                            (p.origPattern, p.name, stateStr))
  171.  
  172.             if p.recipientPattern: recipientPattern = p.recipientPattern.pattern
  173.             else: recipientPattern = ''
  174.             if p.encodingPattern: encodingPattern = p.encodingPattern.pattern
  175.             else: encodingPattern = ''            
  176.             
  177.             strList.append('"%s"\n"%s"' % (recipientPattern, encodingPattern))
  178.  
  179.             for viewID, testRecord in p.testRecords.items():
  180.                 strList.append('%s %s' % (viewID, testRecord))
  181.  
  182.             strList[-1] = '%s\n' % strList[-1]
  183.  
  184.         openFile(self.fn, 'w').write('\n'.join(strList))
  185.  
  186.     def getStatsString (self):
  187.         """Returns a string containing the statistics of all properties. The format:
  188.         'name1 numViews1\n
  189.         view11 cpuTime11 numTruePositive11 numTrueNegative11 numFalsePositive11 numFalseNegative11\n
  190.         view12 ...\n
  191.         name2 numViews2\n  ...', where the names are double quoted."""
  192.         return '\n'.join(['"%s" %d\n%s' % (name, len(p.testRecords),
  193.                                            '\n'.join(['%s %s' % (view, ' '.join(map(str, testRecord.readAll())))
  194.                                                       for view, testRecord in p.testRecords.items()]))
  195.                           for name, p in self.items()])
  196.  
  197.     def setStats (self, statsString):
  198.         """Update the statistics of the properties. The statsString is produced from
  199.         getStatsString()."""
  200.         if len(statsString) == 0: return
  201.         
  202.         count = 0
  203.         for i in statsString.split('\n'):
  204.             if count == 0:
  205.                 idx = i.rfind(' ')
  206.                 p = self.get(i[1:idx - 1])   # patterns could be gone at this point
  207.                 count = int(i[idx + 1:])
  208.             else:
  209.                 if p:
  210.                     l = i.split(' ')
  211.                     testRecord = p.testRecords.get(l[0])
  212.                     if testRecord:           # a view can be gone at this point
  213.                         testRecord.setAll(float(l[1]), int(l[2]), int(l[3]), int(l[4]), int(l[5]))
  214.                 count -= 1
  215.  
  216.  
  217. class Test (object):
  218.     """A single test - could be a property or a pattern
  219.        ------------------------------------------------
  220.        propertyOrPattern: an instance of Property or Pattern.
  221.        isPattern: True iff it's a pattern.
  222.        isOn: True iff it's on.
  223.        isHard: True iff it's a hard test.
  224.        isHTML: True iff it's only applicable to an HTML message (exists only when isPattern is True).
  225.        view: a string indicating which view we're interested in (exists only when isPattern is True).
  226.     """
  227.     __slots__ = ['propertyOrPattern', 'isPattern', 'isOn', 'isHard', 'isHTML', 'view']
  228.     
  229.     def __init__ (self, propertyOrPattern, parameters):
  230.         # IMPORTANT: the positions in parameters are highly dependent on the way
  231.         # tests file is loaded (see Tests.__init__()).
  232.         stateStr = parameters[1]
  233.         
  234.         self.propertyOrPattern = propertyOrPattern
  235.         self.isPattern = isinstance(propertyOrPattern, Pattern)
  236.         self.isOn = parameters[-1] == '1'     # after this point the source in the parameters is never used/updated
  237.         self.isHard = stateStr[-1] == 'H'     # after this point the source in the parameters is never used/updated
  238.         if self.isPattern:
  239.             self.isHTML = stateStr[0] == 'H'  # after this point the source in the parameters is never used/updated
  240.             self.view = parameters[0]
  241.  
  242.     def getAttribute_ (self, name):
  243.         """This is basically for Obj-C side of PyObjC bridge so we can get at the instance variables"""
  244.         return getattr(self, name)
  245.  
  246.     def setAttribute_withValue_ (self, name, value):
  247.         """This is basically for Obj-C side of PyObjC bridge so we can set an instance variable"""
  248.         setattr(self, name, value)
  249.  
  250.  
  251. class Tests (list):
  252.     """A list of tests (including both properties and patterns)
  253.        --------------------------------------------------------
  254.        properties: an instance of Properties
  255.        patterns: an instance of Patterns
  256.        fn: file name
  257.        """
  258.     def __init__ (self, properties, patterns, testsFN):
  259.         self.properties = properties
  260.         self.patterns = patterns
  261.         self.fn = testsFN
  262.         self.load()
  263.  
  264.     def load (self):
  265.         # this only loads in the tests file (not including properties and patterns)
  266.         del self[:]
  267.         mode = 0
  268.         for l in filter(lambda l:len(l) and l[0] != '#',  map(string.strip, openFile(self.fn))):
  269.             if mode == 0:
  270.                 if l[0] == 'P':
  271.                     # property
  272.                     parameters = l.split(' ')
  273.                     propertyOrPattern = self.properties.get(parameters[0])
  274.                 else:
  275.                     # first line of a pattern
  276.                     pattern = l[1:-1]
  277.                     mode += 1
  278.                     continue
  279.             else:
  280.                 parameters = l.split(' ')
  281.                 propertyOrPattern = self.patterns.get(pattern)
  282.                 mode = 0
  283.  
  284.             if propertyOrPattern:
  285.                 self.append(Test(propertyOrPattern, parameters))
  286.             else:
  287.                 NSLog(u'A test is AWOL.')
  288.  
  289.     def patchPropertiesAgainstDefaults (self):
  290.         """Patch the tests so they contain exactly the same set of properties as those in Defaults/tests.
  291.  
  292.         ASSUMPTION: CALL load() BEFORE YOU CALL THIS!"""
  293.         newProperties = Properties('%sproperties' % DEFAULTS_PATH)
  294.         newPropertySet = sets.Set(newProperties.keys())
  295.         oldPropertySet = sets.Set(self.properties.keys())
  296.  
  297.         addSet = newPropertySet - oldPropertySet
  298.         removeSet = oldPropertySet - newPropertySet
  299.  
  300.         # ====== updating self.properties ======
  301.  
  302.         # STEP 1: remove things from self.properties
  303.         for r in removeSet:
  304.             del self.properties[r]
  305.  
  306.         # STEP 2: add things to self.properties
  307.         for r in addSet:
  308.             self.properties[r] = newProperties[r]
  309.  
  310.         # ====== updating self ======
  311.  
  312.         # STEP 1: remove tests
  313.  
  314.         # testList contains (index, test) tuples *only* for tests that are properties
  315.         testList = filter(lambda i: not i[1].isPattern, enumerate(self))
  316.         
  317.         # removeTestList is an ascendingly sorted index list
  318.         removeTestList = map(lambda i: i[0], filter(lambda i: i[1].propertyOrPattern.__class__.__name__ in removeSet, testList))
  319.         if removeTestList:
  320.             for i in removeTestList[::-1]: del self[i]                       # remove from the end of the list
  321.  
  322.         # STEP 2: add tests
  323.             
  324.         # newTests is used to determine the insertion points for the new properties
  325.         newTests = Tests(newProperties, Patterns('%spatterns' % DEFAULTS_PATH), '%stests' % DEFAULTS_PATH)
  326.  
  327.         # newPropertyDict is a dictionary using tests as keys and property class names as values
  328.         # newPropertyDict records the immediately earlier old test of a new test
  329.         # t1 is a test containing a new property, t2 is t1's immediately earlier test
  330.         # n1/n2 is the property class name of the test t1/t2
  331.         newPropertyDict = {}
  332.         t2 = None
  333.         for t1 in filter(lambda t: not t.isPattern, newTests):
  334.             n1 = t1.propertyOrPattern.__class__.__name__
  335.             if n1 in addSet:
  336.                 # ASSUMPTION: t2 can't be None at this point
  337.                 #   cuz we never introduce a new propertie that's placed at the beginning of
  338.                 #   the test list!
  339.                 n2 = t2.propertyOrPattern.__class__.__name__
  340.                 if n2 in addSet:
  341.                     # t2 is new too
  342.                     newPropertyDict[t1] = newPropertyDict[t2]
  343.                 else:
  344.                     newPropertyDict[t1] = n2
  345.             t2 = t1
  346.  
  347.         if len(newPropertyDict):
  348.             # now we reverse the keys and the values into the newPropertyDict2,
  349.             # and the values are lists now
  350.             newPropertyDict2 = {}
  351.             for k, v in newPropertyDict.items():
  352.                 newPropertyDict2.setdefault(v, []).append(k)
  353.  
  354.             idx = 0
  355.             maxIdx = len(self)
  356.             while len(newPropertyDict2):
  357.                 t2 = self[idx]
  358.                 if not t2.isPattern:
  359.                     n2 = t2.propertyOrPattern.__class__.__name__   # n2 is the class name of an old property
  360.                     l = newPropertyDict2.get(n2)
  361.                     if l:
  362.                         for t1 in l[::-1]:  # t1 is a test containing a new property
  363.                             self.insert(idx + 1, t1)
  364.                             
  365.                         idx += len(l)
  366.                         del newPropertyDict2[n2]
  367.                     
  368.                 idx += 1
  369.  
  370.             self.properties.writeToFile()
  371.             self.writeToFile()
  372.  
  373.     def writeToFile (self):
  374.         """So that we can print the specification of test ordering into a file."""
  375.         strList = []
  376.         for test in self:
  377.             if test.isHard: hardStr = 'H'
  378.             else: hardStr = 'S'
  379.  
  380.             if test.isPattern:
  381.                 if test.isHTML: htmlStr = 'H'
  382.                 else: htmlStr = '_'                
  383.                 strList.append(u'"%s"\n%s %s%s %s\n' % (test.propertyOrPattern.origPattern,
  384.                                                         test.view, htmlStr, hardStr, int(test.isOn)))
  385.             else:
  386.                 strList.append(u'%s %s %s\n' % (test.propertyOrPattern.__class__.__name__,
  387.                                                 hardStr, int(test.isOn)))
  388.         
  389.         openFile(self.fn, 'w').write('\n'.join(strList))
  390.                 
  391.  
  392. if __name__ == '__main__':
  393.     if len(sys.argv) == 1:
  394.         print 'Usage: ./Tests.py <filename>'
  395.         print '   * filename is the name of the file containing email raw source.'
  396.         sys.exit(1)
  397.  
  398.     # init tests
  399.     oldTime = time.time()
  400.     tests = Tests(Properties('%sproperties' % CONF_PATH),
  401.                   Patterns('%spatterns' % CONF_PATH),
  402.                   '%stests' % CONF_PATH)
  403.     print 'Initialization time:', time.time() - oldTime
  404.  
  405.     #tests.patchPropertiesAgainstDefaults()
  406.  
  407.     #print '* All of the tests:'
  408.     #print tests
  409.     #print
  410.     
  411.     msg = Message(open(sys.argv[1]).read())
  412.  
  413.     # executionn here doesn't distinguish hard/soft tests
  414.     print '* Executing the tests:'
  415.     for test in filter(lambda t: t.isOn, tests):
  416.         propertyOrPattern = test.propertyOrPattern
  417.         if test.isPattern:
  418.             view = test.view
  419.             mo, cpuTime = propertyOrPattern.run(msg, view)
  420.             if mo:                
  421.                 print encodeText('- Pattern "%s" matches "%s": %f usec(s)' % (propertyOrPattern.name,
  422.                                                                               mo.group(0),
  423.                                                                               cpuTime))
  424.         else:
  425.             result, cpuTime = propertyOrPattern.run(msg)
  426.             if result is not False:
  427.                 if result is True:
  428.                     s = '- %s' % propertyOrPattern.name
  429.                 else:
  430.                     try:
  431.                         s = encodeText('- %s (%s)' % (propertyOrPattern.name, result))
  432.                     except:
  433.                         s = encodeText('- %s' % propertyOrPattern.name)
  434.                     
  435.                 print '%s: %f usec(s)' % (s, cpuTime)
  436.