b8c60795a4bc59dbe9fc912e2b84f8b7f8c569c5
[patchq.git] / perform-tests.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # This task takes patch series messages off one of the ‘patchq_test_*’
5 # queues and tests the patch series.
6 #
7 # If you run it without arguments, it will pick up patches from each
8 # test queue in turn and test them (but not in parallel).  You can
9 # also list one or more tests on the command line, in which case only
10 # that test or tests are considered.
11 #
12 # This script does not perform tests in parallel, but doesn't care if
13 # other instances of the script are running (even across machines).
14 # Because of the message broker each patch series is only tested once.
15
16 import email
17 import json
18 import mailbox
19 import os
20 import pika
21 import shutil
22 import subprocess
23 import sys
24 import tempfile
25
26 import config
27
28 # Because tests are very long running, turn off heartbeats.
29 # https://stackoverflow.com/questions/14572020/handling-long-running-tasks-in-pika-rabbitmq
30 connection = pika.BlockingConnection(pika.ConnectionParameters(
31     host = config.mq_server, heartbeat_interval=0))
32 channel = connection.channel()
33
34 def ack(method):
35     channel.basic_ack(delivery_tag = method.delivery_tag)
36
37 # Which tests to run?
38 if len(sys.argv) <= 1:
39     tests = config.tests
40 else:
41     tests = []
42     for arg in sys.argv[1:]:
43         if arg not in config.tests:
44             sys.exit("%s is not listed in config.tests" % arg)
45         tests.append(arg)
46
47 pwd = os.getcwd()
48
49 for t in tests:
50     qname = "patchq_test_%s" % t
51     while True:
52         method, _, body = channel.basic_get(queue = qname, no_ack = False)
53         if not method: break
54
55         # Parse the ordered list of messages forming the patch series.
56         msgs = json.loads(body)
57         msgs = [email.message_from_string(m) for m in msgs]
58
59         # This should never happen, but the rest of the code
60         # below assumes number of msgs > 0, so ...
61         if len(msgs) == 0:
62             ack(method)
63             continue
64
65         print ("%s: Running test:" % t)
66
67         # Save them to a temporary directory.
68         # Create large files in /var/tmp
69         tempfile.tempdir = "/var/tmp"
70         dir = tempfile.mkdtemp()
71         os.chdir(dir)
72
73         # Save the patches to files.
74         i = 0
75         args = []
76         args.append("%s/%s.sh" % (pwd, t))
77         last_msg = None
78         for m in msgs:
79             i = i+1
80             filename = ("%05d" % i)
81             args.append(filename)
82             print ("%05d %s" % (i, m['Subject']))
83             with open(filename, "w") as file:
84                 file.write(m.as_string())
85             last_msg = m
86
87         # Run the test.
88         with open("output", "w") as out:
89             r = subprocess.call(args, stdout=out, stderr=out)
90
91         if r == 77:
92             print ("%s: Test skipped" % t)
93         else:
94             # Do a "group reply" to the last email.
95             tos = last_msg.get_all('to', [])
96             ccs = last_msg.get_all('cc', [])
97             from_ = last_msg['From']
98             to = email.utils.getaddresses(tos + ccs + [from_])
99             ref = last_msg['Message-Id']
100             if r == 0:
101                 status = "success"
102             else:
103                 status = "FAILED"
104             subject = "%s %s (was: Re: %s)" % (t, status, last_msg['Subject'])
105             with open("output", "r") as file:
106                 content = file.read()
107
108             body = json.dumps((to, subject, ref, content))
109             channel.basic_publish(exchange = 'patchq_reports',
110                                   routing_key = '',
111                                   body = body)
112
113         # Ack the input message since we have processed it.
114         ack(method)
115
116         shutil.rmtree(dir, ignore_errors = True)