summaryrefslogtreecommitdiffstats
path: root/cppunit-parallelize.py
blob: 25106568d56d95f0edb03515525c073502c6ea34 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/usr/bin/env python
# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
# @author Vegard Sjonfjell
import sys
import argparse
import copy
import os
import subprocess
import time

def parse_arguments():
    argparser = argparse.ArgumentParser(description="Run Vespa cppunit tests in parallell")
    argparser.add_argument("testrunner", type=str, help="Test runner executable")
    argparser.add_argument("--chunks", type=int, help="Number of chunks", default=5)
    args = argparser.parse_args()
    if args.chunks < 1:
        raise RuntimeError, "I require at least one chunk" 

    return args

def take(lst, n):
    return [ lst.pop() for i in xrange(n) ]

def chunkify(lst, chunks):
    lst = copy.copy(lst)
    chunk_size = len(lst) / chunks
    chunk_surplus = len(lst) % chunks

    result = [ take(lst, chunk_size) for i in xrange(chunks) ]
    if chunk_surplus:
        result.append(lst)

    return result

class Process:
    def __init__(self, cmd, group):
        self.group = group
        self.finished = False
        self.output = ""
        self.handle = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            preexec_fn=os.setpgrp)

def build_processes(test_groups):
    valgrind = os.getenv("VALGRIND")
    testrunner = (valgrind, args.testrunner) if valgrind else (args.testrunner,)
    processes = []

    for group in test_groups:
        cmd = testrunner + tuple(group)
        processes.append(Process(cmd, group))

    return processes

def cleanup_processes(processes):
    for proc in processes:
        try:
            proc.handle.kill()
        except OSError as e:
            if e.errno != os.errno.ESRCH: # "No such process"
                print >>sys.stderr, e.message

args = parse_arguments()
test_suites = subprocess.check_output((args.testrunner, "--list")).strip().split("\n")
test_suite_groups = chunkify(test_suites, min(len(test_suites), args.chunks))
processes = build_processes(test_suite_groups)

print "Running %d test suites in %d parallel chunks with ~%d tests each" % (len(test_suites), len(test_suite_groups), len(test_suite_groups[0]))

processes_left = len(processes)
while True:
    try:
        for proc in processes:
            return_code = proc.handle.poll()
            proc.output += proc.handle.stdout.read()

            if return_code == 0:
                proc.finished = True
                processes_left -= 1
                if processes_left > 0:
                    print "%d test suite(s) left" % processes_left
                else:
                    print "All test suites ran successfully"
                    sys.exit(0)
            elif return_code is not None:
                print "One of '%s' test suites failed:" % ", ".join(proc.group)
                print >>sys.stderr, proc.output
                sys.exit(return_code)

            time.sleep(0.01)
    finally:
        cleanup_processes(processes)