#
# Test the acc compiler

import unittest
import subprocess
import os
import sys

gArmInitialized = False
gUseArm = True
gUseX86 = True
gRunOTCCOutput = True
gCompileOTCCANSI = True


def parseArgv():
    global gUseArm
    global gUseX86
    global gRunOTCCOutput
    for arg in sys.argv[1:]:
        if arg == "--noarm":
            print "--noarm: not testing ARM"
            gUseArm = False
        elif arg == "--nox86":
            print "--nox86: not testing x86"
            gUseX86 = False
        elif arg == "--norunotcc":
            print "--norunotcc detected, not running OTCC output"
            gRunOTCCOutput = False
        else:
            print "Unknown parameter: ", arg
            raise "Unknown parameter"
    sys.argv = sys.argv[0:1]

def compile(args):
    proc = subprocess.Popen(["acc"] + args, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
    result = proc.communicate()
    return result

def runCmd(args):
    proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    result = proc.communicate()
    return result[0].strip()

def uname():
    return runCmd(["uname"])

def unameM():
    return runCmd(["uname", "-m"])

def which(item):
    return runCmd(["which", item])

def fileType(item):
    return runCmd(["file", item])

def outputCanRun():
    ft = fileType(which("acc"))
    return ft.find("ELF 32-bit LSB executable, Intel 80386") >= 0

def checkEnvironment():
    global gRunOTCCOutput
    global gCompileOTCCANSI
    osName = uname()
    gRunOTCCOutput = osName == "Linux" and unameM() != "x86_64" and outputCanRun()
    # OSX doesn't have stdin/stdout/stderr accessible through dll load symbols, so
    # we can't compile the ANSI version of the OTCC compiler on OS X.
    gCompileOTCCANSI = osName == "Linux"

def adb(args):
    return runCmd(["adb"] + args)

def setupArm():
    global gArmInitialized
    if gArmInitialized:
        return
    print "Setting up arm"
    adb(["remount"])
    adb(["shell", "rm", "/system/bin/acc"])
    adb(["shell", "mkdir", "/system/bin/accdata"])
    adb(["shell", "mkdir", "/system/bin/accdata/data"])
    # Clear out old data TODO: handle recursion
    adb(["shell", "rm", "/system/bin/accdata/data/*"])
    # Copy over data
    for root, dirs, files in os.walk("data"):
        for d in dirs:
            adb(["shell", "mkdir", os.path.join(root, d)])
        for f in files:
            adb(["push", os.path.join(root, f), os.path.join("/system/bin/accdata", root, f)])
    # Copy over compiler
    adb(["sync"])
    gArmInitialized = True

def compileArm(args):
    setupArm()
    proc = subprocess.Popen(["adb", "shell", "/system/bin/acc"] + args, stdout=subprocess.PIPE)
    result = proc.communicate()
    return result[0].replace("\r","")

def compare(a, b):
    if a != b:
        firstDiff = firstDifference(a, b)
        print "Strings differ at character %d. Common: %s. Difference '%s' != '%s'" % (
            firstDiff, a[0:firstDiff], safeAccess(a, firstDiff), safeAccess(b, firstDiff))

def safeAccess(s, i):
    if 0 <= i < len(s):
        return s[i]
    else:
        return '?'

def firstDifference(a, b):
    commonLen = min(len(a), len(b))
    for i in xrange(0, commonLen):
        if a[i] != b[i]:
            return i
    return commonLen

# a1 and a2 are the expected stdout and stderr.
# b1 and b2 are the actual stdout and stderr.
# Compare the two, sets. Allow any individual line
# to appear in either stdout or stderr. This is because
# the way we obtain output on the ARM combines both
# streams into one sequence.

def compareOuput(a1,a2,b1,b2):
    while True:
        totalLen = len(a1) + len(a2) + len(b1) + len(b2)
        a1, b1 = matchCommon(a1, b1)
        a1, b2 = matchCommon(a1, b2)
        a2, b1 = matchCommon(a2, b1)
        a2, b2 = matchCommon(a2, b2)
        newTotalLen = len(a1) + len(a2) + len(b1) + len(b2)
        if newTotalLen == 0:
            return True
        if newTotalLen == totalLen:
            print "Failed at %d %d %d %d" % (len(a1), len(a2), len(b1), len(b2))
            print "a1", a1
            print "a2", a2
            print "b1", b1
            print "b2", b2
            return False

def matchCommon(a, b):
    """Remove common items from the beginning of a and b,
       return just the tails that are different."""
    while len(a) > 0 and len(b) > 0 and a[0] == b[0]:
        a = a[1:]
        b = b[1:]
    return a, b

def rewritePaths(args):
    return [rewritePath(x) for x in args]

def rewritePath(p):
    """Take a path that's correct on the x86 and convert to a path
       that's correct on ARM."""
    if p.startswith("data/"):
        p = "/system/bin/accdata/" + p
    return p

class TestACC(unittest.TestCase):

    def checkResult(self, out, err, stdErrResult, stdOutResult=""):
        a1 = out.splitlines()
        a2 = err.splitlines()
        b2 = stdErrResult.splitlines()
        b1 = stdOutResult.splitlines()
        self.assertEqual(True, compareOuput(a1,a2,b1,b2))

    def compileCheck(self, args, stdErrResult, stdOutResult="",
                     targets=['arm', 'x86']):
        global gUseArm
        global gUseX86
        targetSet = frozenset(targets)
        if gUseX86 and 'x86' in targetSet:
            out, err = compile(args)
            self.checkResult(out, err, stdErrResult, stdOutResult)
        if gUseArm and 'arm' in targetSet:
            out = compileArm(rewritePaths(args))
            self.checkResult(out, "", stdErrResult, stdOutResult)

    def compileCheckArm(self, args, result):
        self.assertEqual(compileArm(args), result)

    def testCompileReturnVal(self):
        self.compileCheck(["data/returnval-ansi.c"], "")

    def testCompileOTCCANSI(self):
        global gCompileOTCCANSI
        if gCompileOTCCANSI:
            self.compileCheck(["data/otcc-ansi.c"], "", "", ['x86'])

    def testRunReturnVal(self):
        self.compileCheck(["-R", "data/returnval-ansi.c"],
        "Executing compiled code:\nresult: 42\n")

    def testContinue(self):
        self.compileCheck(["-R", "data/continue.c"],
        "Executing compiled code:\nresult: 400\n")

    def testStringLiteralConcatenation(self):
        self.compileCheck(["-R", "data/testStringConcat.c"],
        "Executing compiled code:\nresult: 13\n", "Hello, world\n")

    def testRunOTCCANSI(self):
        global gRunOTCCOutput
        if gRunOTCCOutput:
            self.compileCheck(["-R", "data/otcc-ansi.c", "data/returnval.c"],
                "Executing compiled code:\notcc-ansi.c: About to execute compiled code:\natcc-ansi.c: result: 42\nresult: 42\n", "",
                 ['x86'])

    def testRunOTCCANSI2(self):
        global gRunOTCCOutput
        if gRunOTCCOutput:
            self.compileCheck(["-R", "data/otcc-ansi.c", "data/otcc.c", "data/returnval.c"],
                "Executing compiled code:\notcc-ansi.c: About to execute compiled code:\notcc.c: about to execute compiled code.\natcc-ansi.c: result: 42\nresult: 42\n", "",['x86'])

    def testRunConstants(self):
        self.compileCheck(["-R", "data/constants.c"],
            "Executing compiled code:\nresult: 12\n",
            "0 = 0\n010 = 8\n0x10 = 16\n'\\a' = 7\n'\\b' = 8\n'\\f' = 12\n'\\n' = 10\n'\\r' = 13\n'\\t' = 9\n'\\v' = 11\n'\\\\' = 92\n'\\'' = 39\n" +
            "'\\\"' = 34\n'\\?' = 63\n'\\0' = 0\n'\\1' = 1\n'\\12' = 10\n'\\123' = 83\n'\\x0' = 0\n'\\x1' = 1\n'\\x12' = 18\n'\\x123' = 291\n'\\x1f' = 31\n'\\x1F' = 31\n")

    def testRunFloat(self):
        self.compileCheck(["-R", "data/float.c"],
            "Executing compiled code:\nresult: 0\n",
            """Constants: 0 0 0 0.01 0.01 0.1 10 10 0.1
int: 1 float: 2.2 double: 3.3
 ftoi(1.4f)=1
 dtoi(2.4)=2
 itof(3)=3
 itod(4)=4
globals: 1 2 3 4
args: 1 2 3 4
locals: 1 2 3 4
cast rval: 2 4
cast lval: 1.1 2 3.3 4
""")

    def testRunFlops(self):
        self.compileCheck(["-R", "data/flops.c"],
            """Executing compiled code:
result: 0""",
"""-1.1 = -1.1
!1.2 = 0
!0 = 1
double op double:
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
1 / 2 = 0.5
float op float:
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
1 / 2 = 0.5
double op float:
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
1 / 2 = 0.5
double op int:
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
1 / 2 = 0.5
int op double:
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
1 / 2 = 0.5
double op double:
1 op 2: < 1   <= 1   == 0   >= 0   > 0   != 1
1 op 1: < 0   <= 1   == 1   >= 1   > 0   != 0
2 op 1: < 0   <= 0   == 0   >= 1   > 1   != 1
double op float:
1 op 2: < 1   <= 1   == 0   >= 0   > 0   != 1
1 op 1: < 0   <= 1   == 1   >= 1   > 0   != 0
2 op 1: < 0   <= 0   == 0   >= 1   > 1   != 1
float op float:
1 op 2: < 1   <= 1   == 0   >= 0   > 0   != 1
1 op 1: < 0   <= 1   == 1   >= 1   > 0   != 0
2 op 1: < 0   <= 0   == 0   >= 1   > 1   != 1
int op double:
1 op 2: < 1   <= 1   == 0   >= 0   > 0   != 1
1 op 1: < 0   <= 1   == 1   >= 1   > 0   != 0
2 op 1: < 0   <= 0   == 0   >= 1   > 1   != 1
double op int:
1 op 2: < 1   <= 1   == 0   >= 0   > 0   != 1
1 op 1: < 0   <= 1   == 1   >= 1   > 0   != 0
2 op 1: < 0   <= 0   == 0   >= 1   > 1   != 1
branching: 1 0 1
testpassi: 1 2 3 4 5 6 7 8 9 10 11 12
testpassf: 1 2 3 4 5 6 7 8 9 10 11 12
testpassd: 1 2 3 4 5 6 7 8 9 10 11 12
testpassi: 1 2 3 4 5 6 7 8 9 10 11 12
testpassf: 1 2 3 4 5 6 7 8 9 10 11 12
testpassd: 1 2 3 4 5 6 7 8 9 10 11 12
testpassi: 1 2 3 4 5 6 7 8 9 10 11 12
testpassf: 1 2 3 4 5 6 7 8 9 10 11 12
testpassd: 1 2 3 4 5 6 7 8 9 10 11 12
testpassidf: 1 2 3
""")
    def testCasts(self):
        self.compileCheck(["-R", "data/casts.c"],
            """Executing compiled code:
result: 0""", """Reading from a pointer: 3 3
Writing to a pointer: 4
Testing casts: 3 3 4.5 4
Testing reading (int*): 4
Testing writing (int*): 8 9
Testing reading (char*): 0x78 0x56 0x34 0x12
Testing writing (char*): 0x87654321
f(10)
Function pointer result: 70
Testing read/write (float*): 8.8 9.9
Testing read/write (double*): 8.8 9.9
""")

    def testChar(self):
        self.compileCheck(["-R", "data/char.c"], """Executing compiled code:
result: 0""", """a = 99, b = 41
ga = 100, gb = 44""")

    def testTypedef(self):
        self.compileCheck(["-R", "data/typedef.c"], """Executing compiled code:
result: 0""", """x = 3
(4, 6) = (1, 2) + (3, 4)
""")

    def testPointerArithmetic(self):
        self.compileCheck(["-R", "data/pointers.c"], """Executing compiled code:
result: 0""", """Pointer difference: 1 4
Pointer addition: 2
Pointer comparison to zero: 0 0 1
Pointer comparison: 1 0 0 0 1
""")
    def testRollo3(self):
        self.compileCheck(["-R", "data/rollo3.c"], """Executing compiled code:
result: 10""", """""")

    def testFloatDouble(self):
        self.compileCheck(["-R", "data/floatdouble.c"], """Executing compiled code:
result: 0""", """0.002 0.1 10""")

    def testIncDec(self):
        self.compileCheck(["-R", "data/inc.c"], """Executing compiled code:
0
1
2
1
1
2
1
0
result: 0
""","""""")

    def testIops(self):
        self.compileCheck(["-R", "data/iops.c"], """Executing compiled code:
result: 0""", """Literals: 1 -1
++
0
1
2
3
4
5
6
7
8
9
--
10
9
8
7
6
5
4
3
2
1
0
""")

    def testFilm(self):
        self.compileCheck(["-R", "data/film.c"], """Executing compiled code:
result: 0""", """testing...
Total bad: 0
""")

    def testMacros(self):
        self.compileCheck(["-R", "data/macros.c"], """Executing compiled code:
result: 0""", """A = 6
A = 10
""")

    def testpointers2(self):
        self.compileCheck(["-R", "data/pointers2.c"], """Executing compiled code:
result: 0""", """a = 0, *pa = 0
a = 2, *pa = 2
a = 0, *pa = 0 **ppa = 0
a = 2, *pa = 2 **ppa = 2
a = 0, *pa = 0 **ppa = 0
a = 2, *pa = 2 **ppa = 2
""")

    def testassignmentop(self):
        self.compileCheck(["-R", "data/assignmentop.c"], """Executing compiled code:
result: 0""", """2 *= 5  10
20 /= 5  4
17 %= 5  2
17 += 5  22
17 -= 5  12
17<<= 1  34
17>>= 1  8
17&= 1  1
17^= 1  16
16|= 1  17
*f() = *f() + 10;
f()
f()
a = 10
*f() += 10;
f()
a = 10
""")

    def testcomma(self):
        self.compileCheck(["-R", "data/comma.c"], """Executing compiled code:
result: 0""", """statement: 10
if: a = 0
while: b = 11
for: b = 22
return: 30
arg: 12
""")

    def testBrackets(self):
        self.compileCheck(["-R", "data/brackets.c"], """Executing compiled code:
Errors: 0
2D Errors: 0
result: 0
""","""""")

    def testShort(self):
        self.compileCheck(["-R", "data/short.c"], """Executing compiled code:
result: -2
""","""""")

    def testAssignment(self):
        self.compileCheck(["-R", "data/assignment.c"], """Executing compiled code:
result: 7
""","""""")

    def testArray(self):
        self.compileCheck(["-R", "data/array.c"], """Executing compiled code:
localInt: 3
localDouble: 3 3
globalChar: 3
globalDouble: 3
testArgs: 0 2 4
testDecay: Hi!
test2D:
abcdefghijdefghijklm
defghijklmghijklmnop
ghijklmnopjklmnopabc
jklmnopabcmnopabcdef
mnopabcdefpabcdefghi
pabcdefghicdefghijkl
cdefghijklfghijklmno
fghijklmnoijklmnopab
ijklmnopablmnopabcde
lmnopabcdefghijklmno
result: 0
""","""""")

    def testDefines(self):
        self.compileCheck(["-R", "data/defines.c"], """Executing compiled code:
result: 3
""","""""")

    def testFuncArgs(self):
        self.compileCheck(["-R", "data/funcargs.c"], """Executing compiled code:
result: 4
""","""""")

    def testB2071670(self):
        self.compileCheck(["-R", "data/b2071670.c"], """Executing compiled code:
result: 1092616192
""","""""")

    def testStructs(self):
        self.compileCheck(["-R", "data/structs.c"], """Executing compiled code:
testCopying: 37 == 37
testUnion: 1 == 0x3f800000
testArgs: (6, 8, 10, 12)
result: 6
""","""""")

    def testAddressOf(self):
        self.compileCheck(["-R", "data/addressOf.c"], """Executing compiled code:
testStruct: 10 10 10
testArray: 1 1 1
result: 0
""","""""")

def main():
    checkEnvironment()
    parseArgv()
    unittest.main()

if __name__ == '__main__':
    main()