aboutsummaryrefslogtreecommitdiffstats
path: root/build/scripts/run_junit.py
blob: 5f56403bedf628a4e020f8c581103fbd734d275d (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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import collections
import json
import time
import os
import sys

SHUTDOWN_SIGNAL = 'SIGUSR1'

PROVIDES = {
    "devtools/junit-runner/devtools-junit-runner.jar": "junit-runner",
    "devtools/junit5-runner/devtools-junit5-runner.jar": "junit-runner",
}


class SignalInterruptionError(Exception):
    pass


def on_shutdown(s, f):
    raise SignalInterruptionError()


def get_tracefile_path(args):
    return args[args.index('--output') + 1]


def dump_chunk_error(tracefile, name, imps):
    with open(tracefile, 'a') as afile:
        msg = {
            "timestamp": time.time(),
            "name": "chunk-event",
            "value": {
                "errors": [
                    [
                        "fail",
                        "[[bad]]Test contains conflicting dependencies for [[imp]]{}[[bad]]: {}[[rst]]".format(
                            name, ', '.join(imps)
                        ),
                    ],
                ],
            },
        }
        json.dump(msg, afile)
        afile.write("\n")


def verify_classpath(args):
    cpfile = args[args.index('-classpath') + 1]
    assert cpfile.startswith('@'), cpfile

    cpfile = cpfile[1:]
    assert os.path.exists(cpfile)

    with open(cpfile) as afile:
        data = afile.read().splitlines()

    collisions = collections.defaultdict(set)
    for cp in data:
        if cp in PROVIDES:
            collisions[PROVIDES[cp]].add(cp)

    for name, imps in collisions.items():
        if len(imps) > 1:
            tracefile = get_tracefile_path(args)
            dump_chunk_error(tracefile, name, imps)
            return False
    return True


def main():
    args = sys.argv[1:]

    # Emulates PROVIDES(X) for junit-runner and junit5-runner.
    # For more info see DEVTOOLSSUPPORT-7454
    if not verify_classpath(args):
        return 1

    def execve():
        os.execve(args[0], args, os.environ)

    jar_binary = args[args.index('--jar-binary') + 1]
    java_bin_dir = os.path.dirname(jar_binary)
    jstack_binary = os.path.join(java_bin_dir, 'jstack')

    if not os.path.exists(jstack_binary):
        sys.stderr.write("jstack is missing: {}\n".format(jstack_binary))
        execve()

    import signal

    signum = getattr(signal, SHUTDOWN_SIGNAL, None)

    if signum is None:
        execve()

    import subprocess

    proc = subprocess.Popen(args)
    signal.signal(signum, on_shutdown)
    timeout = False

    try:
        proc.wait()
    except SignalInterruptionError:
        sys.stderr.write("\nGot {} signal: going to shutdown junit\n".format(signum))
        # Dump stack traces
        subprocess.call([jstack_binary, str(proc.pid)], stdout=sys.stderr)
        # Kill junit - for more info see DEVTOOLS-7636
        os.kill(proc.pid, signal.SIGKILL)
        proc.wait()
        timeout = True

    if proc.returncode:
        sys.stderr.write('java exit code: {}\n'.format(proc.returncode))
        if timeout:
            # In case of timeout return specific exit code
            # https://a.yandex-team.ru/arc/trunk/arcadia/devtools/ya/test/const/__init__.py?rev=r8578188#L301
            proc.returncode = 10
            sys.stderr.write('java exit code changed to {}\n'.format(proc.returncode))

    return proc.returncode


if __name__ == '__main__':
    exit(main())