import unittest
import json
import tempfile
from time import sleep
from shutil import rmtree
from os.path import join
from os import environ, mkdir
environ['OG_ENGINE_DIR']='./ogr_test_engine'

# modify path, needed for job_executor and libogr
from sys import path
path += ('..', '../../ogr')
from job_executer import Job
import libogr

class TestJobRequest(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_FastCommandJob(self):
        '''
        Tests the simplest and dumbest job, one that executes a shell command
        that finishes fast and only returns the command output.
        '''

        class LsJob(Job):
            '''
            Very simple job that executes ls in a controled environment (a
            temporal directory we create) and checks that the updates sent are
            correct.
            '''
            def __init__(self):
                '''
                Initialices the LsJob by creating the directory and the files
                that will be listed, forge the request, and call the parent
                constructor.
                '''
                self.send_update_called = False
                self.tmp_dir = tempfile.mkdtemp()
                for i in xrange(10):
                    open(join(self.tmp_dir, str(i)), "w").close()
                request = {
                    'type': 'command',
                    'command': 'ls',
                    'args': [self.tmp_dir],
                    'sequential': False
                }
                request_str = json.dumps(request)
                super(LsJob, self).__init__(0, request_str)

            def __del__(self):
                '''
                Clean the tmp dir
                '''
                rmtree(self.tmp_dir)
                if not self.send_update_called:
                    raise Exception("send update was not called")

            def send_progress_update(self, progress, message):
                '''
                Updates are sent either because the command executed in this job
                require it (forcing an update) or because of timeout. LsJob
                shouldn't send any of these update.
                '''
                raise Exception

            def send_update(self, status, message):
                '''
                The only update the ls job should send is one with
                status = FINISHED and message = "0 1 2 3 4 5 6 7 8 9 10"
                (the list of files in tmp_dir)
                '''
                if self.send_update_called:
                    raise Exception("send update called twice")
                if status != "FINISHED" or message != "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n":
                    raise Exception("invalid send update")
                self.send_update_called = True
        job = LsJob()
        sleep(1) # more than enough
        del job

    def test_SlowJob(self):
        '''
        Tests a simple command job that is so slow that the protocol will send
        updates to the server so that the job doesn't timeout.
        '''
        from job_executer import MAX_INTERVAL_TIMEOUT

        class SleepJob(Job):
            '''
            A job that executes the sleep command for a long time so that it
            timesout, but only once.
            '''
            def __init__(self):
                request = {
                    'type': 'command',
                    'command': 'sleep',
                    'args': [str(MAX_INTERVAL_TIMEOUT*1.5)],
                    'sequential': False
                }
                request_str = json.dumps(request)
                self.send_update_called = False
                self.send_update_progress_called = False
                super(SleepJob, self).__init__(0, request_str)

            def __del__(self):
                if not self.send_update_called:
                    raise Exception("send update was not called")
                if not self.send_update_progress_called:
                    raise Exception("send update progress was not called")

            def send_progress_update(self, progress, message):
                '''
                This kind of progress update is activated by the command output
                and is not sent in this usecase.
                '''
                raise Exception

            def send_update(self, status, message):
                '''
                The only update the ls job should send is one with
                status = FINISHED and message = "" (the output of sleep command)
                '''
                if status == "INPROGRESS":
                    if message != "" or self.send_update_called or\
                        self.send_update_progress_called:
                        raise Exception("invalid progress update")
                    self.send_update_progress_called = True
                elif status == "FINISHED":
                    if message != "" or self.send_update_called or\
                        not self.send_update_progress_called:
                        raise Exception("invalid final update")
                    self.send_update_called = True
                else:
                    raise Exception("invalud status update")

        job = SleepJob()
        # sleep enough so that the job gets done
        sleep(MAX_INTERVAL_TIMEOUT*1.5 + 1)
        del job

    def test_OgrJob(self):
        '''
        Tests a job that executes a command using libogr.
        '''

        class OgrJob(Job):
            '''
            A job that executes a simple ogr command.
            '''
            def __init__(self):
                request = {
                    'type': 'ogr',
                    'command': 'simple.sh',
                    'sequential': False
                }
                self.send_update_called = False
                request_str = json.dumps(request)
                super(OgrJob, self).__init__(0, request_str)

            def __del__(self):
                if not self.send_update_called:
                    raise Exception("send update was not called")

            def send_progress_update(self, progress, message):
                '''
                This kind of progress update is activated by the command output
                and is not sent in this usecase.
                '''
                raise Exception('send progress update shouldn\'t be called')

            def send_update(self, status, message):
                '''
                The only update the ls job should send is one with
                status = FINISHED and message = "" (the output of sleep command)
                '''
                if status != "FINISHED" or message != "simple output\n"\
                    or self.send_update_called:
                    raise Exception("invalid update")
                self.send_update_called = True

        job = OgrJob()
        # sleep enough so that the job gets done
        sleep(1)
        del job

    def test_ProgressJob(self):
        '''
        Tests a job that executes a command using libogr that sends progress
        updates.
        '''

        class ProgressJob(Job):
            '''
            A job that executes an ogr command that sends progress updates.
            '''
            def __init__(self):
                request = {
                    'type': 'ogr',
                    'command': 'with_progress.sh',
                    'sequential': False
                }
                self.updates_counter = 0
                request_str = json.dumps(request)
                super(ProgressJob, self).__init__(0, request_str)

            def __del__(self):
                if self.updates_counter != 3:
                    raise Exception("only %d updates were processed" %\
                        self.updates_counter)

            def send_progress_update(self, progress, message):
                if self.updates_counter == 0:
                    if progress != "10" or message != "progress":
                        raise Exception("invalid first progress update")
                elif self.updates_counter == 1:
                    if progress != "70" or message != "some progress":
                        raise Exception("invalid second progress update")
                else:
                    raise Exception("invalid progress update")
                self.updates_counter += 1

            def send_update(self, status, message):
                if self.updates_counter == 2:
                    if status != "FINISHED" or message != "simple output\n":
                        raise Exception("invalid send update data")
                else:
                    raise Exception("invalid send update")
                self.updates_counter += 1

        job = ProgressJob()
        # sleep enough so that the job gets done
        sleep(1)
        del job

    def test_SequentialJobs(self):
        '''
        Tests that jobs marked as sequential are actually going to be executed
        sequentially.
        '''

        class SequentialJob(Job):
            '''
            A job that executes an ogr command that sends progress updates.
            '''
            job_count = 0
            last_finished_job = -1
            def __init__(self):
                request = {
                    'type': 'command',
                    'command': 'sleep',
                    'args': ['1'],
                    'sequential': True
                }
                request_str = json.dumps(request)

                self.job_number = SequentialJob.job_count
                SequentialJob.job_count += 1
                super(SequentialJob, self).__init__(0, request_str)

            def execute_command(self):
                if SequentialJob.last_finished_job != self.job_number - 1:
                    raise Exception('job executed non sequentially')
                super(SequentialJob, self).execute_command()

            def send_progress_update(self, progress, message):
                '''
                Should not send any progress update
                '''
                raise Exception("send progress update called")

            def send_update(self, status, message):
                if status == "FINISHED":
                    SequentialJob.last_finished_job = self.job_number

        SequentialJob()
        SequentialJob()
        SequentialJob()
        SequentialJob()
        # sleep enough so that the jobs gets done
        sleep(5)
        if SequentialJob.last_finished_job != 3:
            raise Exception

    def test_SimultaneousJobs(self):
        '''
        Tests that jobs marked as non-sequential are actually going to be
        executed non-sequentially.
        '''

        class SimultaneousJob(Job):
            '''
            A job that executes an ogr command that sends progress updates.
            '''
            def __init__(self):
                request = {
                    'type': 'command',
                    'command': 'sleep',
                    'args': ['1'],
                    'sequential': False
                }
                request_str = json.dumps(request)
                self.has_finished = False
                super(SimultaneousJob, self).__init__(0, request_str)

            def send_progress_update(self, progress, message):
                '''
                Should not send any progress update
                '''
                raise Exception("send update called")

            def send_update(self, status, message):
                if status == "FINISHED":
                    self.has_finished = True

        jobs = [SimultaneousJob() for i in xrange(10)]
        
        # sleep enough so that the jobs gets done
        sleep(2)
        for job in jobs:
            if not job.has_finished:
                raise Exception

    def test_FilesJob(self):
        '''
        Tests a job that receives input files
        '''

        class FilesJob(Job):
            '''
            A job that executes an ogr command that sends progress updates.
            '''
            def __init__(self):
                request = {
                    'type': 'command',
                    'command': 'cat',
                    'args': ['$1', '$0', '$1'],
                    'files': [
                        'data in file1\nin file1',
                        '''
                        <xml>
                            <example>
                                Wooho file2
                            </example>
                        </xml>
                        ''',
                    ],
                    'sequential': False
                }
                self.has_finished = False
                self.expected_output = request['files'][1] +\
                    request['files'][0] + request['files'][1]
                request_str = json.dumps(request)
                super(FilesJob, self).__init__(0, request_str)

            def __del__(self):
                if not self.has_finished:
                    raise Exception

            def send_progress_update(self, progress, message):
                raise Exception

            def send_update(self, status, message):
                if not self.has_finished:
                    if status != "FINISHED" or message != self.expected_output:
                        raise Exception("invalid send update")
                else:
                    raise Exception("send update called twice")
                self.has_finished = True

        job = FilesJob()
        # sleep enough so that the job gets done
        sleep(1)
        del job

def suite():
   suite = unittest.TestSuite()
   suite.addTest(unittest.makeSuite(TestJobRequest))
   return suite
