667c3fff5aa4cc5727186c36d03fd304799dfec5
[oweals/gnunet.git] / src / integration-tests / gnunet_testing.py.in
1 #!@PYTHON@
2 #    This file is part of GNUnet.
3 #    (C) 2010, 2017, 2018 Christian Grothoff (and other contributing authors)
4 #
5 #    GNUnet is free software: you can redistribute it and/or modify it
6 #    under the terms of the GNU Affero General Public License as published
7 #    by the Free Software Foundation, either version 3 of the License,
8 #    or (at your option) any later version.
9 #
10 #    GNUnet is distributed in the hope that it will be useful, but
11 #    WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #    Affero General Public License for more details.
14 #
15 #    You should have received a copy of the GNU Affero General Public License
16 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 #    SPDX-License-Identifier: AGPL3.0-or-later
19 #
20 # Functions for integration testing
21
22 from builtins import object
23 from builtins import str
24 import os
25 import subprocess
26 import sys
27 import shutil
28 import time
29 from gnunet_pyexpect import pexpect
30 import logging
31
32 logger = logging.getLogger()
33 handler = logging.StreamHandler()
34 formatter = logging.Formatter(
35         '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
36 handler.setFormatter(formatter)
37 logger.addHandler(handler)
38 logger.setLevel(logging.DEBUG)
39
40 class Check(object):
41     def __init__(self, test):
42         self.fulfilled = False
43         self.conditions = list()
44         self.test = test
45
46     def add(self, condition):
47         self.conditions.append(condition)
48
49     def run(self):
50         fulfilled = True
51         pos = 0
52         neg = 0
53         for c in self.conditions:
54             if (False == c.check()):
55                 fulfilled = False
56                 neg += 1
57             else:
58                 pos += 1
59         return fulfilled
60
61     def run_blocking(self, timeout, pos_cont, neg_cont):
62         execs = 0
63         res = False
64         while ((False == res) and (execs < timeout)):
65             res = self.run()
66             time.sleep(1)
67             execs += 1
68         if ((False == res) and (execs >= timeout)):
69             logger.debug('Check had timeout after %s seconds', str(timeout))
70             neg_cont(self)
71         elif ((False == res) and (execs < timeout)):
72             if (None != neg_cont):
73                 neg_cont(self)
74         else:
75             if (None != pos_cont):
76                 pos_cont(self)
77         return res
78
79     def run_once(self, pos_cont, neg_cont):
80         execs = 0
81         res = False
82         res = self.run()
83         if ((res == False) and (neg_cont != None)):
84             neg_cont(self)
85         if ((res == True) and (pos_cont != None)):
86             pos_cont(self)
87         return res
88
89     def evaluate(self, failed_only):
90         pos = 0
91         neg = 0
92         for c in self.conditions:
93             if (False == c.evaluate(failed_only)):
94                 neg += 1
95             else:
96                 pos += 1
97         logger.debug('%s out of %s conditions fulfilled', str(pos), str(pos+neg))
98         return self.fulfilled
99
100     def reset(self):
101         self.fulfilled = False
102         for c in self.conditions:
103             c.fulfilled = False
104
105
106 class Condition(object):
107     def __init__(self):
108         self.fulfilled = False
109         self.type = 'generic'
110
111     def __init__(self, type):
112         self.fulfilled = False
113         self.type = type
114
115     def check(self):
116         return False
117
118     def evaluate(self, failed_only):
119         if ((self.fulfilled == False) and (failed_only == True)):
120             logger.debug('%s condition for was %s', str(self.type), str(self.fulfilled))
121         elif (failed_only == False):
122             logger.debug('%s condition for was %s', str(self.type), str(self.fulfilled))
123         return self.fulfilled
124
125
126 class FileExistCondition(Condition):
127     def __init__(self, file):
128         self.fulfilled = False
129         self.type = 'file'
130         self.file = file
131
132     def check(self):
133         if (self.fulfilled == False):
134             res = os.path.isfile(self.file)
135             if (res == True):
136                 self.fulfilled = True
137                 return True
138             else:
139                 return False
140         else:
141             return True
142
143     def evaluate(self, failed_only):
144         if ((self.fulfilled == False) and (failed_only == True)):
145             logger.debug('%s confition for file %s was %s', str(self.type), self.file, str(self.fulfilled))
146         elif (failed_only == False):
147             logger.debug('%s confition for file %s was %s', str(self.type), self.file, str(self.fulfilled))
148         return self.fulfilled
149
150
151 class StatisticsCondition(Condition):
152     def __init__(self, peer, subsystem, name, value):
153         self.fulfilled = False
154         self.type = 'statistics'
155         self.peer = peer
156         self.subsystem = subsystem
157         self.name = name
158         self.value = str(value)
159         self.result = -1
160
161     def check(self):
162         if (self.fulfilled == False):
163             self.result = self.peer.get_statistics_value(self.subsystem, self.name)
164             if (self.result == self.value):
165                 self.fulfilled = True
166                 return True
167             else:
168                 return False
169         else:
170             return True
171
172     def evaluate(self, failed_only):
173         if (self.fulfilled == False):
174             fail = " FAIL!"
175             op = " != "
176         else:
177             fail = ""
178             op = " == "
179         if (((self.fulfilled == False) and (failed_only == True)) or (failed_only == False)):
180             logger.debug('%s %s condition in subsystem %s: %s: (expected/real value) %s %s %s %s', self.peer.id[:4].decode("utf-8"), self.peer.cfg, self.subsystem.ljust(12), self.name.ljust(30), self.value, op, self.result, fail)
181         return self.fulfilled
182
183
184 # Specify two statistic values and check if they are equal
185 class EqualStatisticsCondition(Condition):
186     def __init__(self, peer, subsystem, name, peer2, subsystem2, name2):
187         self.fulfilled = False
188         self.type = 'equalstatistics'
189         self.peer = peer
190         self.subsystem = subsystem
191         self.name = name
192         self.result = -1
193         self.peer2 = peer2
194         self.subsystem2 = subsystem2
195         self.name2 = name2
196         self.result2 = -1
197
198     def check(self):
199         if (self.fulfilled == False):
200             self.result = self.peer.get_statistics_value(self.subsystem, self.name)
201             self.result2 = self.peer2.get_statistics_value(self.subsystem2, self.name2)
202             if (self.result == self.result2):
203                 self.fulfilled = True
204                 return True
205             else:
206                 return False
207         else:
208             return True
209
210     def evaluate(self, failed_only):
211         if (((self.fulfilled == False) and (failed_only == True)) or (failed_only == False)):
212             logger.debug('%s %s %s == %s %s %s %s %s', self.peer.id[:4], self.subsystem.ljust(12), self.name.ljust(30), self.result, self.peer2.id[:4], self.subsystem2.ljust(12), self.name2.ljust(30), self.result2)
213         return self.fulfilled
214
215
216 class Test(object):
217     def __init__(self, testname, verbose):
218         self.peers = list()
219         self.verbose = verbose
220         self.name = testname
221         srcdir = "../.."
222         gnunet_pyexpect_dir = os.path.join(srcdir, "contrib/scripts")
223         if gnunet_pyexpect_dir not in sys.path:
224             sys.path.append(gnunet_pyexpect_dir)
225         self.gnunetarm = ''
226         self.gnunetstatistics = ''
227         if os.name == 'posix':
228             self.gnunetarm = 'gnunet-arm'
229             self.gnunetstatistics = 'gnunet-statistics'
230             self.gnunetpeerinfo = 'gnunet-peerinfo'
231         elif os.name == 'nt':
232             self.gnunetarm = 'gnunet-arm.exe'
233             self.gnunetstatistics = 'gnunet-statistics.exe'
234             self.gnunetpeerinfo = 'gnunet-peerinfo.exe'
235         if os.name == "nt":
236             shutil.rmtree(os.path.join(os.getenv("TEMP"), testname), True)
237         else:
238             shutil.rmtree("/tmp/" + testname, True)
239
240     def add_peer(self, peer):
241         self.peers.append(peer)
242
243     def p(self, msg):
244         if (self.verbose == True):
245             print(msg)
246
247
248 class Peer(object):
249     def __init__(self, test, cfg_file):
250         if (False == os.path.isfile(cfg_file)):
251             # print(("Peer cfg " + cfg_file + ": FILE NOT FOUND"))
252             logger.debug('Peer cfg %s : FILE NOT FOUND', cfg_file)
253         self.id = "<NaN>"
254         self.test = test
255         self.started = False
256         self.cfg = cfg_file
257
258     def __del__(self):
259         if (self.started == True):
260             # print('ERROR! Peer using cfg ' + self.cfg + ' was not stopped')
261             logger.debug('ERROR! Peer using cfg %s was not stopped', self.cfg)
262             ret = self.stop()
263             if (False == ret):
264                 # print('ERROR! Peer using cfg ' +
265                 #       self.cfg +
266                 #       ' could not be stopped')
267                 logger.debug('ERROR! Peer using cfg %s could not be stopped', self.cfg)
268                 self.started = False
269             return ret
270         else:
271             return False
272
273     def start(self):
274         os.unsetenv ("XDG_CONFIG_HOME")
275         os.unsetenv ("XDG_DATA_HOME")
276         os.unsetenv ("XDG_CACHE_HOME")
277         self.test.p("Starting peer using cfg " + self.cfg)
278         try:
279             server = subprocess.Popen([self.test.gnunetarm, '-sq', '-c', self.cfg])
280             server.communicate()
281         except OSError:
282             # print("Can not start peer")
283             logger.debug('Can not start peer')
284             self.started = False
285             return False
286         self.started = True
287         test = ''
288         try:
289             server = pexpect()
290             server.spawn(None, [self.test.gnunetpeerinfo, '-c', self.cfg, '-s'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
291             test = server.read("stdout", 1024)
292         except OSError:
293             # print("Can not get peer identity")
294             logger.debug('Can not get peer identity')
295         test = (test.split(b'`')[1])
296         self.id = test.split(b'\'')[0]
297         return True
298
299     def stop(self):
300         if (self.started == False):
301             return False
302         self.test.p("Stopping peer using cfg " + self.cfg)
303         try:
304             server = subprocess.Popen([self.test.gnunetarm, '-eq', '-c', self.cfg])
305             server.communicate()
306         except OSError:
307             # print("Can not stop peer")
308             logger.debug('Can not stop peer')
309             return False
310         self.started = False
311         return True
312
313     def get_statistics_value(self, subsystem, name):
314         server = pexpect()
315         server.spawn(None, [self.test.gnunetstatistics, '-c', self.cfg, '-q', '-n', name, '-s', subsystem], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
316         # server.expect ("stdout", re.compile (r""))
317         test = server.read("stdout", 10240)
318         tests = test.partition(b'\n')
319         # On W32 GNUnet outputs with \r\n, rather than \n
320         if os.name == 'nt' and tests[1] == b'\n' and tests[0][-1] == b'\r':
321             tests = (tests[0][:-1], tests[1], tests[2])
322         tests = tests[0]
323         result = tests.decode("utf-8").strip()
324         logger.debug('running gnunet-statistics %s for %s "/" %s yields %s', self.cfg, name, subsystem, result)
325         if (result.isdigit() == True):
326             return result
327         else:
328             logger.debug('Invalid statistics value: %s is not a number!', result)
329             return -1