blob: 2f7e5bb986046d51edc4747b2dc4fcff3ebab7a2 [file] [log] [blame]
adminbae64d82013-08-01 10:50:15 -07001#!/usr/bin/env python
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +00002'''
adminbae64d82013-08-01 10:50:15 -07003Created on 23-Oct-2012
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +00004Copyright 2012 Open Networking Foundation (ONF)
Jon Hall4ba53f02015-07-29 13:07:41 -07005
Jeremy Songsterae01bba2016-07-11 15:39:17 -07006Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
7the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
8or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
adminbae64d82013-08-01 10:50:15 -07009
10 TestON is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 2 of the License, or
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000013 (at your option) any later version.
adminbae64d82013-08-01 10:50:15 -070014
15 TestON is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
Jon Hall4ba53f02015-07-29 13:07:41 -070021 along with TestON. If not, see <http://www.gnu.org/licenses/>.
adminbae64d82013-08-01 10:50:15 -070022
Jon Hall4ba53f02015-07-29 13:07:41 -070023
adminbae64d82013-08-01 10:50:15 -070024Utilities will take care about the basic functions like :
25 * Extended assertion,
26 * parse_args for key-value pair handling
27 * Parsing the params or topology file.
28
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000029'''
adminbae64d82013-08-01 10:50:15 -070030import re
31from configobj import ConfigObj
adminbae64d82013-08-01 10:50:15 -070032from core import ast as ast
33import smtplib
34
adminbae64d82013-08-01 10:50:15 -070035import email
36import os
37import email.mime.application
Jon Hall095730a2015-12-17 14:57:45 -080038import time
39import random
adminbae64d82013-08-01 10:50:15 -070040
41class Utilities:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000042 '''
adminbae64d82013-08-01 10:50:15 -070043 Utilities will take care about the basic functions like :
44 * Extended assertion,
45 * parse_args for key-value pair handling
46 * Parsing the params or topology file.
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000047 '''
Jon Hall4ba53f02015-07-29 13:07:41 -070048
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000049 def __init__(self):
50 self.wrapped = sys.modules[__name__]
51
52 def __getattr__(self, name):
53 '''
adminbae64d82013-08-01 10:50:15 -070054 This will invoke, if the attribute wasn't found the usual ways.
55 Here it will look for assert_attribute and will execute when AttributeError occurs.
56 It will return the result of the assert_attribute.
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000057 '''
adminbae64d82013-08-01 10:50:15 -070058 try:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000059 return getattr(self.wrapped, name)
adminbae64d82013-08-01 10:50:15 -070060 except AttributeError:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000061 def assertHandling(**kwargs):
62 nameVar = re.match("^assert",name,flags=0)
63 matchVar = re.match("assert(_not_|_)(equals|matches|greater|lesser)",name,flags=0)
adminbae64d82013-08-01 10:50:15 -070064 notVar = 0
65 operators = ""
66
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000067 try :
68 if matchVar.group(1) == "_not_" and matchVar.group(2) :
adminbae64d82013-08-01 10:50:15 -070069 notVar = 1
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000070 operators = matchVar.group(2)
71 elif matchVar.group(1) == "_" and matchVar.group(2):
72 operators = matchVar.group(2)
adminbae64d82013-08-01 10:50:15 -070073 except AttributeError:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000074 if matchVar==None and nameVar:
75 operators ='equals'
76 result = self._assert(NOT=notVar,operator=operators,**kwargs)
adminbae64d82013-08-01 10:50:15 -070077 if result == main.TRUE:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000078 main.log.info("Assertion Passed")
Jon Hall79bec222015-04-30 16:23:30 -070079 main.STEPRESULT = main.TRUE
adminbae64d82013-08-01 10:50:15 -070080 elif result == main.FALSE:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000081 main.log.warn("Assertion Failed")
Jon Hall79bec222015-04-30 16:23:30 -070082 main.STEPRESULT = main.FALSE
83 else:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000084 main.log.error("There is an Error in Assertion")
Jon Hall79bec222015-04-30 16:23:30 -070085 main.STEPRESULT = main.ERROR
adminbae64d82013-08-01 10:50:15 -070086 return result
adminbae64d82013-08-01 10:50:15 -070087 return assertHandling
Jon Hall79bec222015-04-30 16:23:30 -070088
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000089 def _assert (self,**assertParam):
90 '''
adminbae64d82013-08-01 10:50:15 -070091 It will take the arguments :
Jon Hall8ce34e82015-06-05 10:41:45 -070092 expect:'Expected output'
93 actual:'Actual output'
adminbae64d82013-08-01 10:50:15 -070094 onpass:'Action or string to be triggered or displayed respectively when the assert passed'
95 onfail:'Action or string to be triggered or displayed respectively when the assert failed'
96 not:'optional argument to specify the negation of the each assertion type'
97 operator:'assertion type will be defined by using operator. Like equal , greater, lesser, matches.'
Jon Hall8ce34e82015-06-05 10:41:45 -070098
adminbae64d82013-08-01 10:50:15 -070099 It will return the assertion result.
Jon Hall8ce34e82015-06-05 10:41:45 -0700100
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000101 '''
102
103 arguments = self.parse_args(["EXPECT","ACTUAL","ONPASS","ONFAIL","NOT","OPERATOR"],**assertParam)
Jon Hall8ce34e82015-06-05 10:41:45 -0700104
adminbae64d82013-08-01 10:50:15 -0700105 result = 0
106 valuetype = ''
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000107 operation = "not "+ str(arguments["OPERATOR"]) if arguments['NOT'] and arguments['NOT'] == 1 else arguments["OPERATOR"]
108 operators = {'equals':{'STR':'==','NUM':'=='}, 'matches' : '=~', 'greater':'>' ,'lesser':'<'}
Jon Hall8ce34e82015-06-05 10:41:45 -0700109
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000110 expectMatch = re.match('^\s*[+-]?0(e0)?\s*$', str(arguments["EXPECT"]), re.I+re.M)
111 if not ((not expectMatch) and (arguments["EXPECT"]==0)):
adminbae64d82013-08-01 10:50:15 -0700112 valuetype = 'NUM'
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000113 else :
114 if arguments["OPERATOR"] == 'greater' or arguments["OPERATOR"] == 'lesser':
115 main.log.error("Numeric comparison on strings is not possibele")
adminbae64d82013-08-01 10:50:15 -0700116 return main.ERROR
Jon Hall8ce34e82015-06-05 10:41:45 -0700117
adminbae64d82013-08-01 10:50:15 -0700118 valuetype = 'STR'
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000119 arguments["ACTUAL"] = str(arguments["ACTUAL"])
120 if arguments["OPERATOR"] != 'matches':
121 arguments["EXPECT"] = str(arguments["EXPECT"])
Jon Hall8ce34e82015-06-05 10:41:45 -0700122
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000123 try :
124 opcode = operators[str(arguments["OPERATOR"])][valuetype] if arguments["OPERATOR"] == 'equals' else operators[str(arguments["OPERATOR"])]
Jon Hall8ce34e82015-06-05 10:41:45 -0700125
Jon Hall1306a562015-09-04 11:21:24 -0700126 except KeyError as e:
adminbae64d82013-08-01 10:50:15 -0700127 print "Key Error in assertion"
Jon Hall1306a562015-09-04 11:21:24 -0700128 print e
adminbae64d82013-08-01 10:50:15 -0700129 return main.FALSE
Jon Hall8ce34e82015-06-05 10:41:45 -0700130
adminbae64d82013-08-01 10:50:15 -0700131 if opcode == '=~':
132 try:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000133 assert re.search(str(arguments["EXPECT"]),str(arguments["ACTUAL"]))
adminbae64d82013-08-01 10:50:15 -0700134 result = main.TRUE
135 except AssertionError:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000136 try :
137 assert re.match(str(arguments["EXPECT"]),str(arguments["ACTUAL"]))
adminbae64d82013-08-01 10:50:15 -0700138 result = main.TRUE
139 except AssertionError:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000140 main.log.error("Assertion Failed")
adminbae64d82013-08-01 10:50:15 -0700141 result = main.FALSE
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000142 else :
adminbae64d82013-08-01 10:50:15 -0700143 try:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000144 if str(opcode)=="==":
145 main.log.info("Verifying the Expected is equal to the actual or not using assert_equal")
146 if (arguments["EXPECT"] == arguments["ACTUAL"]):
adminbae64d82013-08-01 10:50:15 -0700147 result = main.TRUE
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000148 else :
adminbae64d82013-08-01 10:50:15 -0700149 result = main.FALSE
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000150 elif str(opcode) == ">":
151 main.log.info("Verifying the Expected is Greater than the actual or not using assert_greater")
152 if (ast.literal_eval(arguments["EXPECT"]) > ast.literal_eval(arguments["ACTUAL"])) :
adminbae64d82013-08-01 10:50:15 -0700153 result = main.TRUE
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000154 else :
adminbae64d82013-08-01 10:50:15 -0700155 result = main.FALSE
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000156 elif str(opcode) == "<":
157 main.log.info("Verifying the Expected is Lesser than the actual or not using assert_lesser")
158 if (ast.literal_eval(arguments["EXPECT"]) < ast.literal_eval(arguments["ACTUAL"])):
adminbae64d82013-08-01 10:50:15 -0700159 result = main.TRUE
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000160 else :
adminbae64d82013-08-01 10:50:15 -0700161 result = main.FALSE
adminbae64d82013-08-01 10:50:15 -0700162 except AssertionError:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000163 main.log.error("Assertion Failed")
adminbae64d82013-08-01 10:50:15 -0700164 result = main.FALSE
adminbae64d82013-08-01 10:50:15 -0700165 result = result if result else 0
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000166 result = not result if arguments["NOT"] and arguments["NOT"] == 1 else result
adminbae64d82013-08-01 10:50:15 -0700167 resultString = ""
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000168 if result :
169 resultString = str(resultString) + "PASS"
170 main.log.info(arguments["ONPASS"])
171 else :
172 resultString = str(resultString) + "FAIL"
173 if not isinstance(arguments["ONFAIL"],str):
174 eval(str(arguments["ONFAIL"]))
175 else :
176 main.log.error(arguments["ONFAIL"])
177 main.log.report(arguments["ONFAIL"])
Jon Hall90627612015-06-09 14:57:02 -0700178 main.onFailMsg = arguments[ 'ONFAIL' ]
Jon Hall8ce34e82015-06-05 10:41:45 -0700179
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000180 msg = arguments["ON" + str(resultString)]
adminbae64d82013-08-01 10:50:15 -0700181
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000182 if not isinstance(msg,str):
adminbae64d82013-08-01 10:50:15 -0700183 try:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000184 eval(str(msg))
Jon Hall1306a562015-09-04 11:21:24 -0700185 except SyntaxError as e:
186 print "function definition is not right"
187 print e
adminbae64d82013-08-01 10:50:15 -0700188
189 main.last_result = result
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000190 if main.stepResults[2]:
191 main.stepResults[2][-1] = result
Jon Hall80577e72015-09-29 14:07:25 -0700192 try:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000193 main.stepResults[3][-1] = arguments[ 'ONFAIL' ]
Jon Hall80577e72015-09-29 14:07:25 -0700194 except AttributeError:
195 pass
196 else:
197 main.log.warn( "Assertion called before a test step" )
adminbae64d82013-08-01 10:50:15 -0700198 return result
Jon Hall8ce34e82015-06-05 10:41:45 -0700199
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000200 def parse_args(self,args, **kwargs):
201 '''
202 It will accept the (key,value) pair and will return the (key,value) pairs with keys in uppercase.
203 '''
adminbae64d82013-08-01 10:50:15 -0700204 newArgs = {}
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000205 for key,value in kwargs.iteritems():
206 if isinstance(args,list) and str.upper(key) in args:
Jon Hall4ba53f02015-07-29 13:07:41 -0700207 for each in args:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000208 if each==str.upper(key):
209 newArgs [str(each)] = value
210 elif each != str.upper(key) and (newArgs.has_key(str(each)) == False ):
211 newArgs[str(each)] = None
Jon Hall4ba53f02015-07-29 13:07:41 -0700212
adminbae64d82013-08-01 10:50:15 -0700213 return newArgs
Jon Hall4ba53f02015-07-29 13:07:41 -0700214
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000215 def send_mail(self):
adminbae64d82013-08-01 10:50:15 -0700216 # Create a text/plain message
217 msg = email.mime.Multipart.MIMEMultipart()
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000218 try :
adminbae64d82013-08-01 10:50:15 -0700219 if main.test_target:
Jon Hall25079782015-10-13 13:54:39 -0700220 sub = "Result summary of \"" + main.TEST + "\" run on component \"" +\
221 main.test_target + "\" Version \"" +\
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000222 vars( main )[main.test_target].get_version() + "\": " +\
Jon Hall25079782015-10-13 13:54:39 -0700223 str( main.TOTAL_TC_SUCCESS ) + "% Passed"
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000224 else :
Jon Hall25079782015-10-13 13:54:39 -0700225 sub = "Result summary of \"" + main.TEST + "\": " +\
226 str( main.TOTAL_TC_SUCCESS ) + "% Passed"
Jon Hall1306a562015-09-04 11:21:24 -0700227 except ( KeyError, AttributeError ):
Jon Hall25079782015-10-13 13:54:39 -0700228 sub = "Result summary of \"" + main.TEST + "\": " +\
229 str( main.TOTAL_TC_SUCCESS ) + "% Passed"
Jon Hall4ba53f02015-07-29 13:07:41 -0700230
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000231 msg['Subject'] = sub
232 msg['From'] = main.sender
233 msg['To'] = main.mail
Jon Hall4ba53f02015-07-29 13:07:41 -0700234
adminbae64d82013-08-01 10:50:15 -0700235 # The main body is just another attachment
Jon Hall25079782015-10-13 13:54:39 -0700236 body = email.mime.Text.MIMEText( main.logHeader + "\n" +
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000237 main.testResult)
Jon Hall25079782015-10-13 13:54:39 -0700238 msg.attach( body )
Jon Hall4ba53f02015-07-29 13:07:41 -0700239
Jon Hall25079782015-10-13 13:54:39 -0700240 # Attachments
241 for filename in os.listdir( main.logdir ):
242 filepath = main.logdir + "/" + filename
243 fp = open( filepath, 'rb' )
244 att = email.mime.application.MIMEApplication( fp.read(),
245 _subtype="" )
adminbae64d82013-08-01 10:50:15 -0700246 fp.close()
Jon Hall25079782015-10-13 13:54:39 -0700247 att.add_header( 'Content-Disposition',
248 'attachment',
249 filename=filename )
250 msg.attach( att )
251 try:
252 smtp = smtplib.SMTP( main.smtp )
253 smtp.starttls()
254 smtp.login( main.sender, main.senderPwd )
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000255 smtp.sendmail( msg['From'], [msg['To']], msg.as_string() )
Jon Hall25079782015-10-13 13:54:39 -0700256 smtp.quit()
257 except Exception:
258 main.log.exception( "Error sending email" )
Jon Hall4ba53f02015-07-29 13:07:41 -0700259 return main.TRUE
260
Jon Hall25079782015-10-13 13:54:39 -0700261 def send_warning_email( self, subject=None ):
262 try:
263 if not subject:
264 subject = main.TEST + " PAUSED!"
265 # Create a text/plain message
266 msg = email.mime.Multipart.MIMEMultipart()
267
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000268 msg['Subject'] = subject
269 msg['From'] = main.sender
270 msg['To'] = main.mail
Jon Hall25079782015-10-13 13:54:39 -0700271
272 smtp = smtplib.SMTP( main.smtp )
273 smtp.starttls()
274 smtp.login( main.sender, main.senderPwd )
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000275 smtp.sendmail( msg['From'], [msg['To']], msg.as_string() )
Jon Hall25079782015-10-13 13:54:39 -0700276 smtp.quit()
277 except Exception:
278 main.log.exception( "" )
279 return main.FALSE
280 return main.TRUE
Jon Hall4ba53f02015-07-29 13:07:41 -0700281
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000282 def parse(self,fileName):
283 '''
adminbae64d82013-08-01 10:50:15 -0700284 This will parse the params or topo or cfg file and return content in the file as Dictionary
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000285 '''
adminbae64d82013-08-01 10:50:15 -0700286 self.fileName = fileName
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000287 matchFileName = re.match(r'(.*)\.(cfg|params|topo)',self.fileName,re.M|re.I)
adminbae64d82013-08-01 10:50:15 -0700288 if matchFileName:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000289 try :
290 parsedInfo = ConfigObj(self.fileName)
adminbae64d82013-08-01 10:50:15 -0700291 return parsedInfo
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000292 except StandardError:
293 print "There is no such file to parse "+fileName
adminbae64d82013-08-01 10:50:15 -0700294 else:
Jon Hall4ba53f02015-07-29 13:07:41 -0700295 return 0
adminbae64d82013-08-01 10:50:15 -0700296
Jon Hall095730a2015-12-17 14:57:45 -0800297 def retry( self, f, retValue, args=(), kwargs={},
Devin Lima4f95bc2017-08-11 11:13:03 -0700298 sleep=1, attempts=2, randomTime=False,
299 getRetryingTime=False ):
Jon Hall095730a2015-12-17 14:57:45 -0800300 """
301 Given a function and bad return values, retry will retry a function
302 until successful or give up after a certain number of attempts.
303
304 Arguments:
305 f - a callable object
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000306 retValue - Return value(s) of f to retry on. This can be a list or an
Jon Hall095730a2015-12-17 14:57:45 -0800307 object.
308 args - A tuple containing the arguments of f.
309 kwargs - A dictionary containing the keyword arguments of f.
310 sleep - Time in seconds to sleep between retries. If random is True,
311 this is the max time to wait. Defaults to 1 second.
312 attempts - Max number of attempts before returning. If set to 1,
313 f will only be called once. Defaults to 2 trys.
314 random - Boolean indicating if the wait time is random between 0
315 and sleep or exactly sleep seconds. Defaults to False.
316 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000317 # TODO: be able to pass in a conditional statement(s). For example:
Jon Hall095730a2015-12-17 14:57:45 -0800318 # retCondition = "< 7"
319 # Then we do something like 'if eval( "ret " + retCondition ):break'
320 try:
321 assert attempts > 0, "attempts must be more than 1"
322 assert sleep >= 0, "sleep must be >= 0"
323 if not isinstance( retValue, list ):
324 retValue = [ retValue ]
Devin Lima4f95bc2017-08-11 11:13:03 -0700325 if getRetryingTime:
326 startTime = time.time()
Jon Hall095730a2015-12-17 14:57:45 -0800327 for i in range( 0, attempts ):
328 ret = f( *args, **kwargs )
329 if ret not in retValue:
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000330 # NOTE that False in [ 0 ] == True
Jon Hall095730a2015-12-17 14:57:45 -0800331 break
332 if randomTime:
333 sleeptime = random.randint( 0, sleep )
334 else:
335 sleeptime = sleep
336 time.sleep( sleeptime )
Jon Hall2ad817e2017-05-18 11:13:10 -0700337 if i > 0:
338 main.log.debug( str( f ) + " was retried " + str( i ) + " times." )
Devin Lima4f95bc2017-08-11 11:13:03 -0700339 if getRetryingTime:
340 main.log.debug( "Took " + str( time.time() - startTime ) + " seconds for retrying." )
Jon Hall095730a2015-12-17 14:57:45 -0800341 return ret
342 except AssertionError:
343 main.log.exception( "Invalid arguements for retry: " )
Devin Lim44075962017-08-11 10:56:37 -0700344 main.cleanAndExit()
Jon Hall095730a2015-12-17 14:57:45 -0800345 except Exception:
346 main.log.exception( "Uncaught exception in retry: " )
Devin Lim44075962017-08-11 10:56:37 -0700347 main.cleanAndExit()
Jon Hall095730a2015-12-17 14:57:45 -0800348
adminbae64d82013-08-01 10:50:15 -0700349
350if __name__ != "__main__":
351 import sys
Jon Hall4ba53f02015-07-29 13:07:41 -0700352
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000353 sys.modules[__name__] = Utilities()