#!/usr/bin/env python2 # # (C) Copyright 2015-2024 Sei Lisa. All rights reserved. # # This file is part of LSL PyOptimizer. # # LSL PyOptimizer is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # LSL PyOptimizer is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LSL PyOptimizer. If not, see . # Unit testing program. # Checks all files in unit_tests/*.suite/ with extensions .lsl or .run. # When one is found, it's considered a test (if both exist, they are considered # a single test). # # Extension .lsl is for source files to test. If the first line starts with # "// " then the rest of the line is taken as the docstring of that test # (visible with Eric or with option -v). The script is also fed as standard # input to the program. # .run defines the command-line parameters for invocation. A test can be run # without a .lsl file but with a .run file. If not present, the .lsl file # is run with the command line 'main.py -'. The quoting rules are sh-style. # The executable name is ignored, but needs to be present. # .out is for expected output to stdout. If the first line is "REGEX", then # the rest of the file is interpreted as a regular expression that the # output is matched against. Otherwise the output must exactly match. # If the file is not present, that's equivalent to an empty file, i.e. no # output is expected. # .err is like .out but for expected output to stderr, with the same features. # .skp is for a file that if present, will skip this test. The contents are # displayed as the reason for being skipped. # .fail is for a file that, when present, marks the test as expected to fail. # Its contents go to the docstring if not empty, replacing the .lsl one. # # A test passes when the stdout output matches the .out file, and the stderr # output matches the .err file. Both default to empty strings. # import unittest import sys import os #import math import main import glob import re try: import difflib except ImportError: difflib = None if sys.version_info.major < 3: from StringIO import StringIO as StringStream else: from io import BytesIO as StringStream from lslopt import lslcommon,lslfuncs,lslparse,lsloutput,lslloadlib from lslopt.lslcommon import nr from strutil import * class EArgError(Exception): pass def parseArgs(s): """Parse a command line, Bourne shell-style""" if s is None: return None args = [] # States Space = 0 # Space between args. SBackslash = 1 # Backslash after space. Returns to Space if followed # by LF, otherwise inserts the character verbatim and # goes to Normal. Normal = 2 # Normal argument. NBackslash = 3 # Backslash in Normal mode. Returns to Normal if # followed by LF, otherwise inserts the character # verbatim. DQuote = 4 # Double quote mode. DQBackslash = 5 # Backslash in double quote mode. Returns to DQuote if # followed by LF; inserts the character verbatim if # followed by '"', '`', '$' or '\' and inserts a '\' # plus the character verbatim in any other case. SQuote = 6 # Single quote mode. State = Space p = 0 Len = len(s) arg = '' while p < Len: c = s[p:p+1] p += 1 if State in (Space, Normal): if c == '\\': State = NBackslash if State == Normal else SBackslash elif c == '"': State = DQuote elif c == "'": State = SQuote elif c in (' ', '\t'): if State == Normal: State = Space args.append(arg) arg = '' # else remain in the 'Space' state elif c == '\n': break else: State = Normal arg += c elif State in (SBackslash, NBackslash, DQBackslash): if c == '\n': State = (DQuote if State == DQBackslash else Space if State == SBackslash else Normal) else: if State == DQBackslash and c not in ('"', '`', '$', '\\'): arg += '\\' arg += c State = DQuote if State == DQBackslash else Normal elif State == DQuote: if c == '\\': State = DQBackslash # ` and $ are not interpreted by this parser. elif c == '"': State = Normal else: arg += c elif State == SQuote: if c == "'": State = Normal else: arg += c if State in (SQuote, DQuote, DQBackslash): raise EArgError(u"Unterminated string in .run file") if State in (SBackslash, NBackslash): raise EArgError(u"Backslash before EOF in .run file") if State == Normal: args.append(arg) return args #import codecs ## sh-style argument parsing ## identify line continuations #cont_re = re.compile( '\\\\\n' # '|(?:\.|[^ \t\n\'])' # r"|'[^']*'") ## separates words #args_re = re.compile(r'(?:' # r'\\.' # '|[^ \t\n\'"]' # r'|"(?:\\.|[^"])*"' # r"|'[^']*'" # r')+') ## matches types of parts of a word ('...', "...", \x, x) #part_re = re.compile(r'(?:' # r'\\.' # '|[^ \t\'"]' # r')+' # r'|"(?:\\.|[^"])*"' # r"|'[^']*'") # # args = args_re.findall(s) # for i in range(len(args)): # arg = args[i] # argout = '' # for match in part_re.finditer(arg): # part = match.group() # if part[0] == '"': # argout += codecs.escape_decode(part[1:-1])[0] # elif part[0] == "'": # argout += part[1:-1] # else: # argout += codecs.escape_decode(part)[0] # args[i] = argout # return args def tryRead(fn, Binary = False): result = None try: f = open(fn, 'rb' if Binary else 'r') try: result = f.read() finally: f.close() except IOError as e: if e.errno != 2: raise return result # In StringIO, mixing unicode and str causes problems with non-ASCII chars. # Avoid it by overriding the write method, to always encode unicode as UTF-8. class StrUTF8IO(StringStream): encoding = 'utf8' def write(self, s): StringStream.write(self, any2b(s)) def invokeMain(argv, stdin = None): """Invoke main.main, substituting stdin, stdout, stderr. Returns tuple with stdout and stderr.""" # Revert globals to initial state lslcommon.LSO = False lslcommon.IsCalc = False lslcommon.Bugs.clear() lslcommon.Bugs.add(6495) lslcommon.save_stdin = sys.stdin lslcommon.save_stdout = sys.stdout lslcommon.save_stderr = sys.stderr stdout_output = None stderr_output = None try: sys.stdin = StringStream(stdin) sys.stdout = StrUTF8IO() sys.stderr = StrUTF8IO() sys.stdin.encoding = 'utf8' sys.stdout.encoding = 'utf8' sys.stderr.encoding = 'utf8' main.main(argv) stdout_output = sys.stdout.getvalue() stderr_output = sys.stderr.getvalue() finally: sys.stdin = lslcommon.save_stdin sys.stdout = lslcommon.save_stdout sys.stderr = lslcommon.save_stderr lslcommon.LSO = False lslcommon.IsCalc = False lslcommon.Bugs.clear() lslcommon.Bugs.add(6495) return (stdout_output, stderr_output) #def tolEqual(actual, expected, tol): # """Strict equality. Like reallyEqual, but a tolerance can # be specified for comparing floats. # """ # if type(actual) != type(expected): # return False # # # Deal with floats (edge cases, tolerance) # if isinstance(actual, float): # # Signs must be equal # if math.copysign(1, actual) != math.copysign(1, expected): # return False # if math.isnan(actual): # # This compares the sign of NaN as well # return math.isnan(expected) # if math.isinf(actual) and math.isinf(expected): # return actual == expected # return abs(actual - expected) <= tol # # # Deal with tuples and lists (item-by-item, recursively) # if isinstance(actual, (tuple, list)): # return all(tolEqual(i1, i2, tol) # for i1, i2 in zip(actual, expected)) # # # Fall back to 'classic' equality # return actual == expected # #def reallyEqual(actual, expected): # """Strictest equality. The types must be equal. For floats, it checks # that the signs are equal, even for -0.0 and for NaNs. For the rest, # it falls back to ==. # """ # return tolEqual(actual, expected, 0.0) # #def reprEqual(self, actual, expected): # """Returns whether the values are equal when comparing their repr's.""" # return repr(actual) == repr(expected) class UnitTestCase(unittest.TestCase): pass class UnitTestRegression(UnitTestCase): def test_regression_misc(self): """Miscellaneous tests that can't be computed or are too difficult to compute with scripts """ sys.stderr.write('\nRunning miscellaneous tests: ') # Test behaviour under BUG-3763 lslcommon.Bugs.add(3763) self.assertEqual(lslfuncs.llXorBase64(u"ABCDABCDABCD", u"ABCD"), u"AAAAAAAAABCT") self.assertEqual(lslfuncs.llXorBase64(u"ABCDABCDABCDABCDABCDABCDABCD", u"ABCD"), u"AAAAAAAAABCTgxCDEJODAAAAABCT") self.assertEqual(lslfuncs.llXorBase64(u"ABCDABCDABCD", u"ABC="), u"AACDEBCDEBCD") self.assertEqual(lslfuncs.llXorBase64(u"AQCDAQCD", u"AQC="), u"AACCAQCC") lslcommon.Bugs.discard(3763) # Check that zstr returns the same type it is passed. self.assertEqual(type(lslfuncs.zstr(lslcommon.Key(u'x\0x'))), lslcommon.Key) def test_regression_ll_json(self): from unit_tests import json # Patch llJsonSetValue, to allow running the test. json.llJsonSetValue = lambda x, y, z: u"***" werr('\nRunning JSON test module: ') save_stdout = sys.stdout save_stderr = sys.stderr actual_stdout = False actual_stderr = False try: sys.stdout = StrUTF8IO() sys.stderr = StrUTF8IO() errs = json.run_tests() actual_stdout = sys.stdout.getvalue() actual_stderr = sys.stderr.getvalue() finally: sys.stdout = save_stdout sys.stderr = save_stderr self.assertLessEqual(errs, 138) expected_stdout = str2b(tryRead('unit_tests/json.out'), 'utf8') expected_stderr = str2b(tryRead('unit_tests/json.err'), 'utf8') try: self.assertTrue(actual_stdout == expected_stdout) except AssertionError: werr(u'Failed' u'\n************ expected stdout:\n') werr(expected_stdout) werr(u'\n************ actual stdout:\n') werr(actual_stdout) if difflib and expected_stdout and actual_stdout \ and not expected_stdout.startswith(b'REGEX\n'): werr(u'\n************ diff:\n' + u'\n'.join(difflib.unified_diff( b2u(expected_stdout).split(u'\n'), b2u(actual_stdout).split(u'\n'), 'expected', 'actual', lineterm='' ))) werr(u'\n************ ') raise try: self.assertTrue(actual_stderr == expected_stderr) except AssertionError: werr(u'Failed' u'\n************ expected stderr:\n') werr(expected_stderr) werr(u'\n************ actual stderr:\n') werr(actual_stderr) if difflib and expected_stderr and actual_stderr \ and not expected_stderr.startswith(b'REGEX\n'): werr(u'\n************ diff:\n' + u'\n'.join(difflib.unified_diff( b2u(expected_stderr).split(u'\n'), b2u(actual_stderr).split(u'\n'), 'expected', 'actual', lineterm='' ))) werr(u'\n************ ') raise assert 'unit_tests.json' in sys.modules del sys.modules['unit_tests.json'] def test_regression_parser(self): """Test the error cases. There are too many to make a test of each.""" sys.stderr.write('\nRunning parser error tests: ') parser = lslparse.parser(lslloadlib.LoadLibrary()) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'f(){integer i;i>>=i;}') self.assertRaises(lslparse.EParseCantChangeState, parser.parse, 'f(){if(1)state default;else;}default{timer(){}}') self.assertRaises(lslparse.EParseCantChangeState, parser.parse, 'f(){if(1);else state default;}default{timer(){}}') self.assertRaises(lslparse.EParseCantChangeState, parser.parse, 'f(){if(1)if(1)state default;else;else;}default{timer(){}}') # Test behaviour of void functions self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){;}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){[];}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){key a=llDie();}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){key a;a=llDie();}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){do;while(llDie());}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){for(;llDie(););}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){while(llDie());}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){if(llDie());}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){if(llDie());else;}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){[llDie()];}}', ('optimize',)) parser.parse('default{timer(){[llDie()];}}') parser.parse('default{timer(){llDie();}}') parser.parse('default{timer(){(llDie());}}') parser.parse('default{timer(){for(llDie();1;llDie());}}', ('optimize',)) # 'return ' works in the same situations as state changes self.assertRaises(lslparse.EParseReturnShouldBeEmpty, parser.parse, 'default{timer(){return llDie();}}') self.assertRaises(lslparse.EParseReturnShouldBeEmpty, parser.parse, 'default{timer(){if(1)return llDie();else;}}') self.assertRaises(lslparse.EParseReturnShouldBeEmpty, parser.parse, 'default{timer(){if(1);else return llDie();}}') self.assertRaises(lslparse.EParseReturnShouldBeEmpty, parser.parse, 'default{timer(){return 1;}}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){if(1)return 1;}}') # Constants with 'c' in symbol table (introduced to solve issue #30) # Some errors are not the expected ones. Most (all?) should be syntax. parser.parse('integer a=LOOP;default{timer(){llOwnerSay(NAK+EOF);}}') parser.parse('integer a=LOOP;default{timer(){llOwnerSay(NAK+EOF);}}', ('prettify',)) parser.parse('default{timer(){LOOP;}}') parser.parse('default{timer(){LOOP;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{touch(integer LOOP){}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){ZERO_VECTOR.x;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){LOOP.x;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){LOOP=1;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){LOOP++;}}', ('prettify',)) # should raise EParseSyntax instead self.assertRaises(lslparse.EParseUndefined, parser.parse, 'default{timer(){++LOOP;}}', ('prettify',)) # should raise EParseSyntax instead self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'integer LOOP=0;', ('prettify',)) # should raise EParseSyntax instead self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'integer LOOP(){}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){integer LOOP=1;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){integer LOOP;}}', ('prettify',)) # should raise EParseSyntax instead self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'default{timer(){}}state LOOP{timer(){}}', ('prettify',)) # should raise EParseSyntax instead self.assertRaises(lslparse.EParseUndefined, parser.parse, 'default{timer(){state LOOP;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){@LOOP;}}', ('prettify',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){integer LOOP;}}', ('prettify',)) class UnitTestCoverage(UnitTestCase): def test_coverage_misc(self): """Miscellaneous tests that can't be computed or are too difficult to compute with scripts """ sys.stderr.write('\nRunning misc coverage tests: ') # Doesn't accept bytes self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.zstr, b"blah") # Can't typecast float to vector self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslfuncs.F32(1.2), lslcommon.Vector) # Can't typecast integer to vector self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, 1, lslcommon.Vector) # Can't typecast vector to key self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslcommon.Vector((1.,2.,3.)), lslcommon.Key) # Can't typecast quaternion to key self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslcommon.Quaternion((1.,2.,3.,4.)), lslcommon.Key) # Can't typecast list to vector self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, [1, 1., lslcommon.Key(u'blah'), lslcommon.Quaternion((1.,0.,0.,0.))], lslcommon.Vector) # Can't typecast key to integer self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslcommon.Key(u"1"), int) # Can't negate string self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.neg, u"3") # Can't add two keys self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.add, lslcommon.Key(u"1"), lslcommon.Key(u"2")) # Can't subtract two strings self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.sub, u"1", u"2") # Can't multiply two strings self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, u"1", u"2") # Can't multiply quaternion and float in any order self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, lslcommon.Quaternion((1.,2.,3.,4.)), 1.) self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, 1., lslcommon.Quaternion((1.,2.,3.,4.))) # Can't multiply quaternion by vector (but the opposite order is OK) self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, lslcommon.Quaternion((1.,2.,3.,4.)), lslcommon.Vector((1.,2.,3.))) # Can't divide quaternion by vector either self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.div, lslcommon.Quaternion((1.,2.,3.,4.)), lslcommon.Vector((1.,2.,3.))) # Can't mod floats self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mod, 3., 3) # Can't compare string and integer self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.compare, u'3', 4) self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.less, u'3', 4) # Bytes is not a valid type to multiply by (in any order) self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.mul, b"a", 3) self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.mul, lslcommon.Vector((3.,4.,5.)), b"a") self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.typecast, b"", unicode) # v2f/q2f coverage (force conversion from ints to floats) self.assertEqual(repr(lslfuncs.v2f(lslcommon.Vector((1,0,0)))), 'Vector((1.0, 0.0, 0.0))') self.assertEqual(repr(lslfuncs.q2f(lslcommon.Quaternion((1,0,0,0)))), 'Quaternion((1.0, 0.0, 0.0, 0.0))') # Key repr coverage self.assertEqual(repr(lslcommon.Key(u'')), "Key(u'')" if str != unicode else "Key('')") # string + key coverage self.assertEqual(lslfuncs.add(u'a', lslcommon.Key(u'b')), u'ab') self.assertEqual(type(lslfuncs.add(u'a', lslcommon.Key(u'b'))), unicode) # The SEF table prevents this assertion from being reachable via script. self.assertRaises(lslfuncs.ELSLCantCompute, lslfuncs.llXorBase64Strings, u"AABA", u"AABA") self.assertRaises(lslfuncs.ELSLCantCompute, lslfuncs.llModPow, 3, 5, 7) # Check invalid type in llGetListEntryType self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.llGetListEntryType, [b'a'], 0) # Check that Value2LSL raises an exception if the type is unknown. outmod = lsloutput.outscript() # Script with a single node of type Expression, containing a constant # of type Bytes. That's rejected by the output module. msg = None script = [nr(nt='EXPR', t='string', ch=[ nr(nt='CONST', t='string', value=b'ab') ])] save_IsCalc = lslcommon.IsCalc lslcommon.IsCalc = True try: try: outmod.output((script, ())) except AssertionError as e: msg = str(e) finally: lslcommon.IsCalc = save_IsCalc self.assertEqual(msg, u"Value of unknown type in Value2LSL: 'ab'" if python2 else u"Value of unknown type in Value2LSL: b'ab'") del msg # Extended assignment in output script = [nr(nt='EXPR', t='integer', ch=[ nr(nt='^=', t='integer', ch=[ nr(nt='IDENT', t='integer', name='a', scope=0), nr(nt='CONST', t='integer', value=3) ])])] save_IsCalc = lslcommon.IsCalc lslcommon.IsCalc = True try: out = outmod.output((script, [{'a':{'Kind':'v','Loc':1,'Scope':0, 'Type':'integer'} }] )) finally: lslcommon.IsCalc = save_IsCalc self.assertEqual(out, 'a = a ^ (3)') del out, script, outmod, save_IsCalc def test_coverage_parser(self): """Cover the error cases. There are too many to make a test of each.""" parser = lslparse.parser(lslloadlib.LoadLibrary( builtins = 'unit_tests/builtins-coverage-2.txt', fndata = 'unit_tests/builtins-coverage-2.txt')) self.assertRaises(lslparse.EParseNoConversion, parser.parse, 'f(){list L;(integer)L[0];}', ('lazylists',)) parser = lslparse.parser(lslloadlib.LoadLibrary()) sys.stderr.write('\nRunning parser exception coverage tests: ') # Parse_unary_postfix_expression self.assertRaises(lslparse.EParseUEOF, parser.parse, u'f(){key x=') self.assertRaises(lslparse.EParseUndefined, parser.parse, 'f(){g();}') self.assertRaises(lslparse.EParseUndefined, parser.parse, 'integer g;f(){g();}') self.assertRaises(lslparse.EParseUndefined, parser.parse, 'f(){f=0;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){integer V; V[1] = 0;}', ('lazylists',)) self.assertRaises(lslparse.EParseFunctionMismatch, parser.parse, 'f(){list V; V[1,1] = 0;}', ('lazylists',)) self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){list V; V[""] = 0;}', ('lazylists',)) self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){list V; V[1] = llDie();}', ('lazylists',)) self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;s++;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;++s;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;s=llDie();}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;s+=(key)"";}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;s-=s;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;s*=2;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){vector v;v%=1.0;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){string s;s>>=s;}', ('extendedassignment',)) # Parse_unary_expression self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){-"";}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){!"";}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){~"";}') self.assertRaises(lslparse.EParseUndefined, parser.parse, 'f(){++f;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){(key)1;}') self.assertRaises(lslparse.EParseFunctionMismatch, parser.parse, 'f(){list L;(integer)L[""];}', ('lazylists',)) # Parse_factor self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""*2;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){<1,1,1>%2;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){<1,1,1>/<1,1,1>;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){<1,1,1>/"";}') # Parse_term self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){llDie()+1;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""-1;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){[]+llDie();}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){(key)""+(key)"";}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""+(key)"";}') # Parse_shift self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){"">>1;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){1<<"";}') # Parse_inequality self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""<"";}') # Parse_comparison self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){llDie()==3;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""==3;}') # Parse_bitbool_factor self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""&3;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){3&"";}') # Parse_bitxor_term self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""^3;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){3^"";}') # Parse_bitbool_term self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""|3;}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){3|"";}') # Parse_expression self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){3||"";}') self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'f(){""&&3;}') # Parse_optional_expression_list self.assertRaises(lslparse.EParseFunctionMismatch, parser.parse, 'f(){llSay(0);}') self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'f(){@x;@x;}') self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'f(){integer x;integer x;}') self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'f(integer x, integer x){}') self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, 'default{timer(){}timer(){}}') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){state state;}}') self.assertRaises(lslparse.EParseUndefined, parser.parse, 'default{timer(){state undefined;}}') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){switch(1){case 1;}}}', ('enableswitch',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){switch(1){default;}}}', ('enableswitch',)) self.assertRaises(lslparse.EParseInvalidBrkContArg, parser.parse, 'default{timer(){while(1){break 0;}}}', ('breakcont',)) self.assertRaises(lslparse.EParseInvalidBrkContArg, parser.parse, 'default{timer(){while(1){break 2;}}}', ('breakcont',)) self.assertRaises(lslparse.EParseInvalidBrkContArg, parser.parse, 'default{timer(){while(1){continue 0;}}}', ('breakcont',)) self.assertRaises(lslparse.EParseInvalidBrkContArg, parser.parse, 'default{timer(){while(1){continue 2;}}}', ('breakcont',)) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'integer T=-TRUE;default{timer(){}}') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'list L=[[]];default{timer(){}}') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(integer i){}}') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'i = 0;',) self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){}}state{timer(){}}') self.assertRaises(lslparse.EParseUndefined, parser.parse, 'default{timer(){jump undefined;}}') # BuildTempGlobalsTable coverage self.assertRaises(lslparse.EParseSyntax, parser.parse, ';') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'f(;') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'f();') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'integer f=') self.assertRaises(lslparse.EParseUEOF, parser.parse, 'integer /*') self.assertRaises(lslparse.EParseSyntax, parser.parse, 'default{timer(){}}state e;') class UnitTestExpr(UnitTestCase): pass class UnitTestLSO(UnitTestCase): pass class UnitTestPreproc(UnitTestCase): pass def generateScriptTests(): """Find all files in unit_tests/*.d/*.{lsl,run} and generate tests for them. """ path = os.path.dirname(__file__) if path: os.chdir(path) testsuites = ('Regression', 'Coverage', 'Expr', 'LSO', 'Preproc') for testsuite in testsuites: files = glob.glob(os.path.join('unit_tests', testsuite.lower() + '.suite', '*.lsl') ) + glob.glob(os.path.join('unit_tests', testsuite.lower() + '.suite', '*.run') ) files = list(set([os.path.splitext(x)[0] for x in files])) files.sort() for fbase in files: # Create a closure with the test data def makeTestFunction(fbase, suite): def TestFunction(self): # We NEED to read in binary, because there's invalid UTF-8 # as part of the tests, and Python 3 would choke otherwise. # This means we have to deal with CRLF line endings here. stdin = (tryRead(fbase + '.lsl', Binary=True) or b'' ).replace(b'\r\n', b'\n') expected_stdout = str2b(tryRead(fbase + '.out') or '', 'utf8') expected_stderr = str2b(tryRead(fbase + '.err') or '', 'utf8') runargs = (parseArgs(tryRead(fbase + '.run')) or (['main.py', '-y', '-'] if suite != 'Expr' else ['main.py', # Defaults for Expr: '-O', 'clear,optimize,constfold' ',addstrings,foldtabs,expr', '-y', '-'])) werr(u"\nRunning test %s: " % any2u(fbase)) actual_stdout, actual_stderr = invokeMain(runargs, stdin) actual_stdout = actual_stdout.replace(b'\r\n', b'\n') actual_stderr = actual_stderr.replace(b'\r\n', b'\n') try: if expected_stderr.startswith(b'REGEX\n'): self.assertIsNotNone(re.search( b2u(expected_stderr[6:], 'utf8'), b2u(actual_stderr, 'utf8')) ) else: self.assertTrue(expected_stderr == actual_stderr) except AssertionError: werr(u'Failed' u'\n************ expected stderr:\n') werr(expected_stderr) werr(u'\n************ actual stderr:\n') werr(actual_stderr) if difflib and expected_stderr and actual_stderr \ and not expected_stderr.startswith(b'REGEX\n'): werr(u'\n************ diff:\n' + u'\n'.join(difflib.unified_diff( b2u(expected_stderr).split(u'\n'), b2u(actual_stderr).split(u'\n'), 'expected', 'actual', lineterm='' ))) werr(u'\n************ ') raise try: if expected_stdout.startswith(b'REGEX\n'): self.assertIsNotNone(re.search( b2u(expected_stdout[6:], 'utf8'), b2u(actual_stdout, 'utf8'))) else: self.assertTrue(expected_stdout == actual_stdout) except AssertionError: werr(u'Failed' u'\n************ expected stdout:\n') werr(expected_stdout) werr(u'\n************ actual stdout:\n') werr(actual_stdout) if difflib and expected_stdout and actual_stdout \ and not expected_stdout.startswith(b'REGEX\n'): werr(u'\n************ diff:\n' + u'\n'.join(difflib.unified_diff( b2u(expected_stdout).split('\n'), b2u(actual_stdout).split('\n'), 'expected', 'actual', lineterm='' ))) sys.stderr.write(u'\n************ ') raise return TestFunction TestFunction = makeTestFunction(fbase, testsuite) # __doc__ is used by Eric line = b'' try: f = open(fbase + '.lsl', 'rb') try: line = f.readline() if line.endswith(b'\r\n'): line = line[:-2] elif line[-1:] in (b'\r', b'\n'): line = line[:-1] finally: f.close() except IOError as e: if e.errno != 2: raise TestFunction.__doc__ = (b2u(line[3:]) if line.startswith(b'// ') else None) TestFunction.__name__ = ('test_' + testsuite + '__' + os.path.basename(fbase).replace('-','_')) fail = tryRead(fbase + '.fail') if fail is not None: if fail: TestFunction.__doc__ = b2u(fail) TestFunction = unittest.expectedFailure(TestFunction) else: skip = tryRead(fbase + '.skp') if skip is not None: TestFunction = unittest.skip(skip)(TestFunction) setattr(globals()['UnitTest' + testsuite], TestFunction.__name__, TestFunction) generateScriptTests() if __name__ == '__main__': unittest.main(argv = sys.argv)