#!/usr/bin/env python3

import argparse
import glob
import subprocess
import select
import os
import os.path
import sys
import time

NUMJOBS=4

class Job(object):
    def __init__(self, dirname, poll):
        self.dirname = dirname
        self.poll = poll
        self.out = b''

    def start(self):
        self.p = subprocess.Popen(
            ['./testsuite.sh'],
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True,
            cwd=self.dirname)
        self.out_fd = self.p.stdout
        self.poll.extend([self.out_fd])

    def wait(self):
        self.poll.remove(self.out_fd)
        return self.p.wait()

def run(keep):
    # List of tests to run
    tests = [n.name for n in os.scandir('.') if n.is_dir()]

    start_time = time.time()
    jobs = []
    poll = []
    failures = []
    nbr_tests = len(tests)
    nbr_run = 0
    nbr_err = 0
    while len(tests) != 0 or len(jobs) != 0:
        # Start as many jobs as possible
        if len(tests) > 0 and len(jobs) < NUMJOBS:
            test = tests.pop(0)
            j = Job(test, poll)
            jobs.append(j)
            print('Starting {} [{}+{} err/{}]'.format(
                test, nbr_run, nbr_err, nbr_tests))
            j.start()
        elif len(jobs) > 0:
            # Wait for output or end of job.
            assert len(poll) == NUMJOBS or len(tests) == 0
            res = select.select(poll, [], [])
            done = set()
            for fd in res[0]:
                d = os.read(fd.fileno(), 1024)
                if len(d) == 0:
                    # EOF => end of job.
                    for j in jobs:
                        if fd == j.out_fd:
                            done.add(j)
                            break
                else:
                    # Gather output
                    for j in jobs:
                        if fd == j.out_fd:
                            j.out += d
            for j in done:
                print('Finish: {}'.format(j.dirname))
                s = j.out
                if sys.version_info[0] >= 3:
                    s = s.decode('latin-1')
                print(s)
                code = j.wait()
                if code != 0:
                    print('############### Error for {}'.format(j.dirname))
                    nbr_err += 1
                    failures.append(j.dirname)
                    if not keep:
                        tests = []
                jobs.remove(j)
                nbr_run += 1
    end_time = time.time()

    print('{}/{} tests run in {} sec, {} failures'.format(
        nbr_run, nbr_tests, end_time - start_time, nbr_err))
    if failures:
        print('Failure: {}'.format(failures))
        return False
    return True


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="parallel test driver")
    parser.add_argument("-k", "--keep",
                        help="keep running", action='store_true')
    parser.add_argument("-j", "--jobs", type=int, default=4,
                        help="number of parallel jobs")
    args = parser.parse_args()
    NUMJOBS = args.jobs
    if not run(args.keep):
        sys.exit(1)
