'''Tests jython.bat using the --print option'''

import os
import sys
import unittest
import tempfile

from test import test_support

from java.lang import IllegalThreadStateException
from java.lang import Runtime
from java.lang import System
from java.lang import Thread
from java.io import File
from java.io import BufferedReader;
from java.io import InputStreamReader;

class Monitor(Thread):
    def __init__(self, process):
        self.process = process
        self.output = ''

    def run(self):
        reader = BufferedReader(InputStreamReader(self.getStream()))
        try:
            line = reader.readLine()
            while line:
                self.output += line
                line = reader.readLine()
        finally:
            reader.close()
    
    def getOutput(self):
        return self.output

class StdoutMonitor(Monitor):
    def __init_(self, process):
        Monitor.__init__(self, process)

    def getStream(self):
        return self.process.getInputStream()

class StderrMonitor(Monitor):
    def __init_(self, process):
        Monitor.__init__(self, process)

    def getStream(self):
        return self.process.getErrorStream()

class StarterProcess:
    def writeStarter(self, args, javaHome, jythonHome, jythonOpts, internals=False):
        (starter, starterPath) = tempfile.mkstemp(suffix='.bat', prefix='starter', text=True)
        starter.close()
        outfilePath = starterPath[:-4] + '.out'
        starter = open(starterPath, 'w') # open starter as simple file
        try:
            if javaHome:
                starter.write('set JAVA_HOME=%s\n' % javaHome)
            if jythonHome:
                starter.write('set JYTHON_HOME=%s\n' % jythonHome)
            if jythonOpts:
                starter.write('set JYTHON_OPTS=%s\n' % jythonOpts)
            if internals:
                starter.write('set _JYTHON_OPTS=leaking_internals\n')
                starter.write('set _JYTHON_HOME=c:/leaking/internals\n')
            starter.write(self.buildCommand(args, outfilePath))
            return (starterPath, outfilePath)
        finally:
            starter.close()

    def buildCommand(self, args, outfilePath):
        line = ''
        for arg in args:
            line += arg
            line += ' '
        line += '> '
        line += outfilePath
        line += ' 2>&1'
        return line

    def getOutput(self, outfilePath):
        lines = ''
        outfile = open(outfilePath, 'r')
        try:
            for line in outfile.readlines():
                lines += line
        finally:
            outfile.close()
        return lines

    def isAlive(self, process):
        try:
            process.exitValue()
            return False
        except IllegalThreadStateException:
            return True

    def run(self, args, javaHome, jythonHome, jythonOpts, internals=False):
        ''' creates a start script, executes it and captures the output '''
        (starterPath, outfilePath) = self.writeStarter(args, javaHome, jythonHome, jythonOpts, internals)
        try:
            process = Runtime.getRuntime().exec(starterPath)
            stdoutMonitor = StdoutMonitor(process)
            stderrMonitor = StderrMonitor(process)
            stdoutMonitor.start()
            stderrMonitor.start()
            while self.isAlive(process):
                Thread.sleep(300)
            return self.getOutput(outfilePath)
        finally:
            os.remove(starterPath)
            os.remove(outfilePath)

class BaseTest(unittest.TestCase):
    def quote(self, s):
        return '"' + s + '"'

    def unquote(self, s):
        if len(s) > 0:
            if s[:1] == '"':
                s = s[1:]
        if len(s) > 0:
            if s[-1:] == '"':
                s = s[:-1]
        return s

    def getHomeDir(self):
        ex = sys.executable
        tail = ex[-15:]
        if tail == '\\bin\\jython.bat':
            home = ex[:-15]
        else:
            home = ex[:-11] # \jython.bat
        return home

    def assertOutput(self, flags=None, javaHome=None, jythonHome=None, jythonOpts=None, internals=False):
        args = [self.quote(sys.executable), '--print']
        memory = None
        stack = None
        prop = None
        jythonArgs = None
        boot = False
        jdb = False
        if flags:
            for flag in flags:
                if flag[:2] == '-J':
                    if flag[2:6] == '-Xmx':
                        memory = flag[6:]
                    elif flag[2:6] == '-Xss':
                        stack = flag[6:]
                    elif flag[2:4] == '-D':
                        prop = flag[2:]
                elif flag[:2] == '--':
                    if flag[2:6] == 'boot':
                        boot = True
                    elif flag[2:5] == 'jdb':
                        jdb = True
                else:
                    if jythonArgs:
                        jythonArgs += ' '
                        jythonArgs += flag
                    else:
                        jythonArgs = flag
                    jythonArgs = jythonArgs.replace('%%', '%') # workaround two .bat files
                args.append(flag)
        process = StarterProcess()
        out = process.run(args, javaHome, jythonHome, jythonOpts, internals)
        self.assertNotEquals('', out)
        homeIdx = out.find('-Dpython.home=')
        java = 'java'
        if javaHome:
            java = self.quote(self.unquote(javaHome) + '\\bin\\java')
        elif jdb:
            java = 'jdb'
        if not memory:
            memory = '512m'
        if not stack:
            stack = '1152k'
        beginning = java + ' '
        if prop:
            beginning += ' ' + prop
        beginning += ' -Xmx' + memory + ' -Xss' + stack + ' '
        self.assertEquals(beginning, out[:homeIdx])
        executableIdx = out.find('-Dpython.executable=')
        homeDir = self.getHomeDir()
        if jythonHome:
            homeDir = self.unquote(jythonHome)
        home = '-Dpython.home=' + self.quote(homeDir) + ' '
        self.assertEquals(home, out[homeIdx:executableIdx])
        if boot:
            classpathFlag = '-Xbootclasspath/a:'
        else:
            classpathFlag = '-classpath'
        classpathIdx = out.find(classpathFlag)
        executable = '-Dpython.executable=' + self.quote(sys.executable) + ' '
        if not boot:
            executable += ' '
        self.assertEquals(executable, out[executableIdx:classpathIdx])
        # ignore full contents of classpath at the moment
        classIdx = out.find('org.python.util.jython')
        self.assertTrue(classIdx > classpathIdx)
        restIdx = classIdx + len('org.python.util.jython')
        rest = out[restIdx:].strip()
        if jythonOpts:
            self.assertEquals(self.quote(jythonOpts), rest)
        else:
            if jythonArgs:
                self.assertEquals(jythonArgs, rest)
            else:
                self.assertEquals('', rest)

class VanillaTest(BaseTest):
    def test_plain(self):
        self.assertOutput()

class JavaHomeTest(BaseTest):
    def test_unquoted(self):
        # for the build bot, try to specify a real java home
        javaHome = System.getProperty('java.home', 'C:\\Program Files\\Java\\someJava')
        self.assertOutput(javaHome=javaHome)

    def test_quoted(self):
        self.assertOutput(javaHome=self.quote('C:\\Program Files\\Java\\someJava'))

    # this currently fails, meaning we accept only quoted (x86) homes ...
    def __test_x86_unquoted(self):
        self.assertOutput(javaHome='C:\\Program Files (x86)\\Java\\someJava')

    def test_x86_quoted(self):
        self.assertOutput(javaHome=self.quote('C:\\Program Files (x86)\\Java\\someJava'))
        
class JythonHomeTest(BaseTest):
    def createJythonJar(self, parentDir):
        jar = File(parentDir, 'jython.jar')
        if not jar.exists():
            self.assertTrue(jar.createNewFile())
        return jar

    def cleanup(self, tmpdir, jar=None):
        if jar and jar.exists():
            self.assertTrue(jar.delete())
        os.rmdir(tmpdir)

    def test_unquoted(self):
        jythonHomeDir = tempfile.mkdtemp()
        jar = self.createJythonJar(jythonHomeDir)
        self.assertOutput(jythonHome=jythonHomeDir)
        self.cleanup(jythonHomeDir, jar)

    def test_quoted(self):
        jythonHomeDir = tempfile.mkdtemp()
        jar = self.createJythonJar(jythonHomeDir)
        self.assertOutput(jythonHome=self.quote(jythonHomeDir))
        self.cleanup(jythonHomeDir, jar)

class JythonOptsTest(BaseTest):
    def test_single(self):
        self.assertOutput(jythonOpts='myOpt')
        
    def test_multiple(self):
        self.assertOutput(jythonOpts='some arbitrary options')

class InternalsTest(BaseTest):
    def test_no_leaks(self):
        self.assertOutput(internals=True)

class JavaOptsTest(BaseTest):
    def test_memory(self):
        self.assertOutput(['-J-Xmx321m'])

    def test_stack(self):
        self.assertOutput(['-J-Xss321k'])

    def test_property(self):
        self.assertOutput(['-J-DmyProperty=myValue'])

    def test_property_singlequote(self):
        self.assertOutput(["-J-DmyProperty='myValue'"]) 

    # a space inside value does not work in jython.bat
    def __test_property_singlequote_space(self):
        self.assertOutput(["-J-DmyProperty='my Value'"])

    def test_property_doublequote(self):
        self.assertOutput(['-J-DmyProperty="myValue"']) 

    # a space inside value does not work in jython.bat
    def __test_property_doublequote_space(self):
        self.assertOutput(['-J-DmyProperty="my Value"'])

    def test_property_underscore(self):
        self.assertOutput(['-J-Dmy_Property=my_Value'])

class ArgsTest(BaseTest):
    def test_file(self):
        self.assertOutput(['test.py'])
    
    def test_dash(self):
        self.assertOutput(['-i'])

    def test_combined(self):
        self.assertOutput(['-W', 'action', 'line'])

    def test_singlequoted(self):
        self.assertOutput(['-c', "'import sys;'"])

    def test_doublequoted(self):
        self.assertOutput(['-c', '"print \'something\'"'])

    def test_nestedquotes(self):
        self.assertOutput(['-c', '"print \'something \"really\" cool\'"'])

    def test_nestedquotes2(self):
        self.assertOutput(['-c', "'print \"something \'really\' cool\"'"])

    def test_underscored(self):
        self.assertOutput(['-jar', 'my_stuff.jar'])
    
    def test_property(self):
        self.assertOutput(['-DmyProperty=myValue'])

    def test_property_underscored(self):
        self.assertOutput(['-DmyProperty=my_Value'])

    def test_property_singlequoted(self):
        self.assertOutput(["-DmyProperty='my_Value'"])

    def test_property_doublequoted(self):
        self.assertOutput(['-DmyProperty="my_Value"'])

class DoubleDashTest(BaseTest):
    def test_boot(self):
        self.assertOutput(['--boot'])

    def test_jdb(self):
        self.assertOutput(['--jdb'])

class GlobPatternTest(BaseTest):
    def test_star_nonexisting(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', '*.nonexisting', '*.nonexisting'])

    def test_star_nonexisting_doublequoted(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', '"*.nonexisting"', '"*.nonexisting"'])

    def test_star_nonexistingfile_singlequoted(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', "'*.nonexisting'", "'*.nonexisting'"])

    def test_star_existing(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', '*.bat', '*.bat'])

    def test_star_existing_doublequoted(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', '"*.bat"', '"*.bat"'])

    def test_star_existing_singlequoted(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', "'*.bat'", "'*.bat'"])

class ArgsSpacesTest(BaseTest):
    def test_doublequoted(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', '"part1 part2"', '2nd'])

    def test_singlequoted(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', "'part1 part2'", '2nd'])

    # this test currently fails
    def __test_unbalanced_doublequote(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', 'Scarlet O"Hara', '2nd'])

    def test_unbalanced_singlequote(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', "Scarlet O'Hara", '2nd'])

class ArgsSpecialCharsTest(BaseTest):
    # exclamation marks are still very special ...
    def __test_exclamationmark(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', 'foo!', 'ba!r', '!baz', '!'])

    # because we go through a starter.bat file, we have to simulate % with %%
    def test_percentsign(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', 'foo%%1', '%%1bar', '%%1', '%%'])

    def test_colon(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', 'foo:', ':bar'])

    # a semicolon at the beginning of an arg currently fails (e.g. ;bar)
    def test_semicolon(self):
        self.assertOutput(['-c', 'import sys; print sys.argv[1:]', 'foo;'])

class DummyTest(unittest.TestCase):
    def test_nothing(self):
        pass

def test_main():
    if os._name == 'nt':
        test_support.run_unittest(VanillaTest,
                                  JavaHomeTest,
                                  JythonHomeTest,
                                  JythonOptsTest,
                                  InternalsTest,
                                  JavaOptsTest,
                                  ArgsTest,
                                  DoubleDashTest,
                                  GlobPatternTest,
                                  ArgsSpacesTest,
                                  ArgsSpecialCharsTest)
    else:
        # provide at least one test for the other platforms - happier build bots
        test_support.run_unittest(DummyTest)


if __name__ == '__main__':
    test_main()
        
