Test Setup Failed
Push — master ( 0e7769...f02296 )
by Ian
04:23
created

build.rsudp.client.main()   D

Complexity

Conditions 12

Size

Total Lines 97
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 44
nop 0
dl 0
loc 97
rs 4.8
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like build.rsudp.client.main() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import sys, os
2
import signal
3
import getopt
4
import time
5
import json
6
import re
7
import logging
8
from queue import Queue
9
from rsudp import printM, printW, printE, default_loc, init_dirs, output_dir, add_debug_handler, start_logging
10
from rsudp import COLOR
11
import rsudp.test as t
12
import rsudp.raspberryshake as rs
13
from rsudp.packetize import packetize
14
from rsudp.c_consumer import Consumer
15
from rsudp.p_producer import Producer
16
from rsudp.c_printraw import PrintRaw
17
from rsudp.c_write import Write
18
from rsudp.c_plot import Plot, MPL
19
from rsudp.c_forward import Forward
20
from rsudp.c_alert import Alert
21
from rsudp.c_alertsound import AlertSound
22
from rsudp.c_custom import Custom
23
from rsudp.c_tweet import Tweeter
24
from rsudp.c_telegram import Telegrammer
25
from rsudp.c_testing import Testing
26
from rsudp.t_testdata import TestData
27
import pkg_resources as pr
28
import fnmatch
29
try:
30
	from pydub import AudioSegment
31
	PYDUB_EXISTS = True
32
except ImportError:
33
	PYDUB_EXISTS = False
34
35
36
DESTINATIONS, THREADS = [], []
37
PROD = False
38
PLOTTER = False
39
SOUND = False
40
TESTING = False
41
TESTQUEUE = False
42
TESTFILE = pr.resource_filename('rsudp', os.path.join('test', 'testdata'))
43
SENDER = 'Main'
44
45
def handler(sig, frame):
46
	'''
47
	Function passed to :py:func:`signal.signal` to handle close events
48
	'''
49
	rs.producer = False
50
51
def _xit(code=0):
52
	'''
53
	End the program. Called after all running threads have stopped.
54
55
	:param int code: The process code to exit with. 0=OK, 1=ERROR.
56
	'''
57
	if TESTING:
58
		TESTQUEUE.put(b'ENDTEST')
59
	for thread in THREADS:
60
		del thread
61
	
62
	printM('Shutdown successful.', sender=SENDER)
63
	print()
64
	sys.exit(code)
65
66
def test_mode(mode=None):
67
	'''
68
	Sets the TESTING global variable to ``True`` to indicate that
69
	testing-specific actions should be taken in routines.
70
71
	:param bool mode: if ``True`` or ``False``, sets testing mode state. if anything else, returns state only.
72
	:return: testing mode state
73
	:rtype: bool
74
	'''
75
	global TESTING
76
	if (mode == True) or (mode == False):
77
		TESTING = mode
78
	return TESTING
79
80
def print_stats():
81
	s = 'Main'
82
	printW('Initialization stats:', s, announce=False)
83
	printW('                Port: %s' % rs.port, sender=SENDER, announce=False)
84
	printW('  Sending IP address: %s' % rs.firstaddr, sender=SENDER, announce=False)
85
	printW('    Set station name: %s' % rs.stn, sender=SENDER, announce=False)
86
	printW('  Number of channels: %s' % rs.numchns, sender=SENDER, announce=False)
87
	printW('  Transmission freq.: %s ms/packet' % rs.tf, sender=SENDER, announce=False)
88
	printW('   Transmission rate: %s packets/sec' % rs.tr, sender=SENDER, announce=False)
89
	printW('  Samples per second: %s sps' % rs.sps, sender=SENDER, announce=False)
90
	if rs.inv:
91
		printW('           Inventory: %s' % rs.inv.get_contents()['stations'][0],
92
			   sender=SENDER, announce=False)
93
94
95
96
def mk_q():
97
	'''
98
	Makes a queue and appends it to the :py:data:`destinations`
99
	variable to be passed to the master consumer thread
100
	:py:class:`rsudp.c_consumer.Consumer`.
101
102
	:rtype: queue.Queue
103
	:return: Returns the queue to pass to the sub-consumer.
104
	'''
105
	q = Queue(rs.qsize)
106
	DESTINATIONS.append(q)
107
	return q
108
109
def mk_p(proc):
110
	'''
111
	Appends a process to the list of threads to start and stop.
112
113
	:param threading.Thread proc: The process thread to append to the list of threads.
114
	'''
115
	THREADS.append(proc)
116
117
118
def start():
119
	'''
120
	Start Consumer, Threads, and Producer.
121
	'''
122
	global PROD, PLOTTER, THREADS, DESTINATIONS
123
	# master queue and consumer
124
	queue = Queue(rs.qsize)
125
	cons = Consumer(queue, DESTINATIONS)
126
	cons.start()
127
128
	for thread in THREADS:
129
		thread.start()
130
131
	PROD = Producer(queue, THREADS)
132
	PROD.start()
133
134
	if PLOTTER and MPL:
135
		# give the plotter the master queue
136
		# so that it can issue a TERM signal if closed
137
		PLOTTER.master_queue = queue
138
		# start plotting (in this thread, not a separate one)
139
		PLOTTER.run()
140
	else:
141
		while not PROD.stop:
142
			time.sleep(0.1) # wait until processes end
143
144
145
	time.sleep(0.5) # give threads time to exit
146
	PROD.stop = True
147
148
149
def run(settings, debug):
150
	'''
151
	Main setup function. Takes configuration values and passes them to
152
	the appropriate threads and functions.
153
154
	:param dict settings: settings dictionary (see :ref:`defaults` for guidance)
155
	:param bool debug: whether or not to show debug output (should be turned off if starting as daemon)
156
	'''
157
	global PLOTTER, SOUND
158
	# handler for the exit signal
159
	signal.signal(signal.SIGINT, handler)
160
161
	if TESTING:
162
		global TESTQUEUE
163
		# initialize the test data to read information from file and put it on the port
164
		TESTQUEUE = Queue()		# separate from client library because this is not downstream of the producer
165
		tdata = TestData(q=TESTQUEUE, data_file=TESTFILE, port=settings['settings']['port'])
166
		tdata.start()
167
168
	# initialize the central library
169
	rs.initRSlib(dport=settings['settings']['port'],
170
				 rsstn=settings['settings']['station'])
171
172
	if TESTING:
173
		t.TEST['n_port'][1] = True	# port has been opened
174
		print_stats()
175
		if rs.sps == 0:
176
			printE('There is already a Raspberry Shake sending data to this port.', sender=SENDER)
177
			printE('For testing, please change the port in your settings file to an unused one.',
178
					sender=SENDER, spaces=True)
179
			_xit(1)
180
181
182
	output_dir = settings['settings']['output_dir']
183
184
185
	if settings['printdata']['enabled']:
186
		# set up queue and process
187
		q = mk_q()
188
		prnt = PrintRaw(q)
189
		mk_p(prnt)
190
191
	if settings['write']['enabled']:
192
		# set up queue and process
193
		cha = settings['write']['channels']
194
		q = mk_q()
195
		writer = Write(q=q, cha=cha)
196
		mk_p(writer)
197
198
	if settings['plot']['enabled'] and MPL:
199
		while True:
200
			if rs.numchns == 0:
201
				time.sleep(0.01)
202
				continue
203
			else:
204
				break
205
		cha = settings['plot']['channels']
206
		sec = settings['plot']['duration']
207
		spec = settings['plot']['spectrogram']
208
		full = settings['plot']['fullscreen']
209
		kiosk = settings['plot']['kiosk']
210
		screencap = settings['plot']['eq_screenshots']
211
		alert = settings['alert']['enabled']
212
		if settings['plot']['deconvolve']:
213
			if settings['plot']['units'].upper() in rs.UNITS:
214
				deconv = settings['plot']['units'].upper()
215
			else:
216
				deconv = 'CHAN'
217
		else:
218
			deconv = False
219
		pq = mk_q()
220
		PLOTTER = Plot(cha=cha, seconds=sec, spectrogram=spec,
221
						fullscreen=full, kiosk=kiosk, deconv=deconv, q=pq,
222
						screencap=screencap, alert=alert)
223
		# no mk_p() here because the plotter must be controlled by the main thread (this one)
224
225
	if settings['forward']['enabled']:
226
		# put settings in namespace
227
		addr = settings['forward']['address']
228
		port = settings['forward']['port']
229
		cha = settings['forward']['channels']
230
		# set up queue and process
231
		q = mk_q()
232
		forward = Forward(addr=addr, port=port, cha=cha, q=q)
233
		mk_p(forward)
234
235
	if settings['alert']['enabled']:
236
		# put settings in namespace
237
		sta = settings['alert']['sta']
238
		lta = settings['alert']['lta']
239
		thresh = settings['alert']['threshold']
240
		reset = settings['alert']['reset']
241
		bp = [settings['alert']['highpass'], settings['alert']['lowpass']]
242
		cha = settings['alert']['channel']
243
		if settings['alert']['deconvolve']:
244
			if settings['alert']['units'].upper() in rs.UNITS:
245
				deconv = settings['alert']['units'].upper()
246
			else:
247
				deconv = 'CHAN'
248
		else:
249
			deconv = False
250
251
		# set up queue and process
252
		q = mk_q()
253
		alrt = Alert(sta=sta, lta=lta, thresh=thresh, reset=reset, bp=bp,
254
					 cha=cha, debug=debug, q=q,
255
					 deconv=deconv)
256
		mk_p(alrt)
257
258
	if settings['alertsound']['enabled']:
259
		sender = 'AlertSound'
260
		SOUND = False
261
		soundloc = False
262
		if PYDUB_EXISTS:
263
			soundloc = os.path.expanduser(os.path.expanduser(settings['alertsound']['mp3file']))
264
			if soundloc in ['doorbell', 'alarm', 'beeps', 'sonar']:
265
				soundloc = pr.resource_filename('rsudp', os.path.join('rs_sounds', '%s.mp3' % soundloc))
266
			if os.path.exists(soundloc):
267
				try:
268
					SOUND = AudioSegment.from_file(soundloc, format="mp3")
269
					printM('Loaded %.2f sec alert sound from %s' % (len(SOUND)/1000., soundloc), sender='AlertSound')
270
				except FileNotFoundError as e:
271
					printW("You have chosen to play a sound, but don't have ffmpeg or libav installed.", sender='AlertSound')
272
					printW('Sound playback requires one of these dependencies.', sender='AlertSound', spaces=True)
273
					printW("To install either dependency, follow the instructions at:", sender='AlertSound', spaces=True)
274
					printW('https://github.com/jiaaro/pydub#playback', sender='AlertSound', spaces=True)
275
					printW('The program will now continue without sound playback.', sender='AlertSound', spaces=True)
276
					SOUND = False
277
			else:
278
				printW("The file %s could not be found." % (soundloc), sender='AlertSound')
279
				printW('The program will now continue without sound playback.', sender='AlertSound', spaces=True)
280
		else:
281
			printW("You don't have pydub installed, so no sound will play.", sender='AlertSound')
282
			printW('To install pydub, follow the instructions at:', sender='AlertSound', spaces=True)
283
			printW('https://github.com/jiaaro/pydub#installation', sender='AlertSound', spaces=True)
284
			printW('Sound playback also requires you to install either ffmpeg or libav.', sender='AlertSound', spaces=True)
285
286
		q = mk_q()
287
		alsnd = AlertSound(q=q, sound=SOUND, soundloc=soundloc)
288
		mk_p(alsnd)
289
290
	runcustom = False
291
	try:
292
		f = False
293
		win_ovr = False
294
		if settings['custom']['enabled']:
295
			# put settings in namespace
296
			f = settings['custom']['codefile']
297
			win_ovr = settings['custom']['win_override']
298
			if f == 'n/a':
299
				f = False
300
			runcustom = True
301
	except KeyError as e:
302
		if settings['alert']['exec'] != 'eqAlert':
303
			printW('the custom code function has moved to its own module (rsudp.c_custom)', sender='Custom')
304
			f = settings['alert']['exec']
305
			win_ovr = settings['alert']['win_override']
306
			runcustom = True
307
		else:
308
			raise KeyError(e)
309
	if runcustom:
310
		# set up queue and process
311
		q = mk_q()
312
		cstm = Custom(q=q, codefile=f, win_ovr=win_ovr)
313
		mk_p(cstm)
314
315
316
	if settings['tweets']['enabled']:
317
		consumer_key = settings['tweets']['api_key']
318
		consumer_secret = settings['tweets']['api_secret']
319
		access_token = settings['tweets']['access_token']
320
		access_token_secret = settings['tweets']['access_secret']
321
		tweet_images = settings['tweets']['tweet_images']
322
323
		q = mk_q()
324
		tweet = Tweeter(q=q, consumer_key=consumer_key, consumer_secret=consumer_secret,
325
						access_token=access_token, access_token_secret=access_token_secret,
326
						tweet_images=tweet_images)
327
		mk_p(tweet)
328
329
	if settings['telegram']['enabled']:
330
		token = settings['telegram']['token']
331
		chat_id = settings['telegram']['chat_id']
332
		send_images = settings['telegram']['send_images']
333
334
		q = mk_q()
335
		telegram = Telegrammer(q=q, token=token, chat_id=chat_id,
336
							   send_images=send_images)
337
		mk_p(telegram)
338
339
	# start additional modules here!
340
	################################
341
342
343
	################################
344
345
	if TESTING:
346
		# initialize test consumer
347
		q = mk_q()
348
		test = Testing(q=q)
349
		mk_p(test)
350
351
352
	# start the producer, consumer, and activated modules
353
	start()
354
355
	PLOTTER = False
356
	if not TESTING:
357
		_xit()
358
	else:
359
		printW('Client has exited, ending tests...', sender=SENDER, announce=False)
360
		if SOUND:
361
			t.TEST['d_pydub'][1] = True
362
363
364
def dump_default(settings_loc, default_settings):
365
	'''
366
	Dumps a default settings file to a specified location.
367
368
	:param str settings_loc: The location to create the new settings JSON.
369
	:param str default_settings: The default settings to dump to file.
370
	'''
371
	if not TESTING:
372
		print('Creating a default settings file at %s' % settings_loc)
373
	with open(settings_loc, 'w+') as f:
374
		f.write(default_settings)
375
		f.write('\n')
376
377
	if TESTING:
378
		return True
379
380
381
def default_settings(output_dir='%s/rsudp' % os.path.expanduser('~').replace('\\', '/'), verbose=True):
382
	'''
383
	Returns a formatted json string of default settings.
384
385
	:param str output_dir: the user's specified output location. defaults to ``~/rsudp``.
386
	:param bool verbose: if ``True``, displays some information as the string is created.
387
	:return: default settings string in formatted json
388
	:rtype: str
389
	'''
390
	def_settings = r"""{
391
"settings": {
392
    "port": 8888,
393
    "station": "Z0000",
394
    "output_dir": "%s",
395
    "debug": true},
396
"printdata": {
397
    "enabled": false},
398
"write": {
399
    "enabled": false,
400
    "channels": ["all"]},
401
"plot": {
402
    "enabled": true,
403
    "duration": 30,
404
    "spectrogram": true,
405
    "fullscreen": false,
406
    "kiosk": false,
407
    "eq_screenshots": false,
408
    "channels": ["HZ", "HDF"],
409
    "deconvolve": false,
410
    "units": "CHAN"},
411
"forward": {
412
    "enabled": false,
413
    "address": "192.168.1.254",
414
    "port": 8888,
415
    "channels": ["all"]},
416
"alert": {
417
    "enabled": true,
418
    "channel": "HZ",
419
    "sta": 6,
420
    "lta": 30,
421
    "threshold": 1.7,
422
    "reset": 1.6,
423
    "highpass": 0,
424
    "lowpass": 50,
425
    "deconvolve": false,
426
    "units": "VEL"},
427
"alertsound": {
428
    "enabled": false,
429
    "mp3file": "doorbell"},
430
"custom": {
431
    "enabled": false,
432
    "codefile": "n/a",
433
    "win_override": false},
434
"tweets": {
435
    "enabled": false,
436
    "tweet_images": true,
437
    "api_key": "n/a",
438
    "api_secret": "n/a",
439
    "access_token": "n/a",
440
    "access_secret": "n/a"},
441
"telegram": {
442
    "enabled": false,
443
    "send_images": true,
444
    "token": "n/a",
445
    "chat_id": "n/a"}
446
}
447
448
""" % (output_dir)
449
	if verbose:
450
		print('By default output_dir is set to %s' % output_dir)
451
	return def_settings
452
453
454
def read_settings(loc):
455
	'''
456
	Reads settings from a specific location.
457
458
	:param str loc: location on disk to read json settings file from
459
	:return: settings dictionary read from JSON, or ``None``
460
	:rtype: dict or NoneType
461
	'''
462
	settings_loc = os.path.abspath(os.path.expanduser(loc)).replace('\\', '/')
463
	settings = None
464
	with open(settings_loc, 'r') as f:
465
		try:
466
			data = f.read().replace('\\', '/')
467
			settings = json.loads(data)
468
		except Exception as e:
469
			print(COLOR['red'] + 'ERROR: Could not load settings file. Perhaps the JSON is malformed?' + COLOR['white'])
470
			print(COLOR['red'] + '       detail: %s' % e + COLOR['white'])
471
			print(COLOR['red'] + '       If you would like to overwrite and rebuild the file, you can enter the command below:' + COLOR['white'])
472
			print(COLOR['bold'] + '       shake_client -d %s' % loc + COLOR['white'])
473
			exit(2)
474
	return settings
475
476
477
def main():
478
	'''
479
	Loads settings to start the main client.
480
	Supply -h from the command line to see help text.
481
	'''
482
	settings_loc = os.path.join(default_loc, 'rsudp_settings.json').replace('\\', '/')
483
484
	hlp_txt='''
485
###########################################
486
##     R A S P B E R R Y  S H A K E      ##
487
##              UDP Client               ##
488
##            by Ian Nesbitt             ##
489
##            GNU GPLv3 2020             ##
490
##                                       ##
491
## Do various tasks with Shake data      ##
492
## like plot, trigger alerts, and write  ##
493
## to miniSEED.                          ##
494
##                                       ##
495
##  Requires:                            ##
496
##  - numpy, obspy, matplotlib 3, pydub  ##
497
##                                       ##
498
###########################################
499
500
Usage: rs-client [ OPTIONS ]
501
where OPTIONS := {
502
    -h | --help
503
            display this help message
504
    -d | --dump=default or /path/to/settings/json
505
            dump the default settings to a JSON-formatted file
506
    -s | --settings=/path/to/settings/json
507
            specify the path to a JSON-formatted settings file
508
    }
509
510
rs-client with no arguments will start the program with
511
settings in %s
512
''' % settings_loc
513
514
515
	settings = json.loads(default_settings(verbose=False))
516
517
	# get arguments
518
	try:
519
		opts = getopt.getopt(sys.argv[1:], 'hid:s:',
520
			['help', 'install', 'dump=', 'settings=']
521
			)[0]
522
	except Exception as e:
523
		print(COLOR['red'] + 'ERROR: %s' % e + COLOR['white'])
524
		print(hlp_txt)
525
526
	if len(opts) == 0:
527
		if not os.path.exists(settings_loc):
528
			print(COLOR['yellow'] + 'Could not find rsudp settings file, creating one at %s' % settings_loc + COLOR['white'])
529
			dump_default(settings_loc, default_settings())
530
		else:
531
			settings = read_settings(settings_loc)
532
533
	for o, a in opts:
534
		if o in ('-h', '--help'):
535
			print(hlp_txt)
536
			exit(0)
537
		if o in ('-i', '--install'):
538
			'''
539
			This is only meant to be used by the install script.
540
			'''
541
			os.makedirs(default_loc, exist_ok=True)
542
			dump_default(settings_loc, default_settings(output_dir='@@DIR@@', verbose=False))
543
			exit(0)
544
		if o in ('-d', '--dump='):
545
			'''
546
			Dump the settings to a file, specified after the `-d` flag, or `-d default` to let the software decide where to put it.
547
			'''
548
			if str(a) in 'default':
549
				os.makedirs(default_loc, exist_ok=True)
550
				dump_default(settings_loc, default_settings())
551
			else:
552
				dump_default(os.path.abspath(os.path.expanduser(a)), default_settings())
553
			exit(0)
554
		if o in ('-s', 'settings='):
555
			'''
556
			Start the program with a specific settings file, for example: `-s settings.json`.
557
			'''
558
			settings = read_settings(a)
559
560
	start_logging()
561
	debug = settings['settings']['debug']
562
	if debug:
563
		add_debug_handler()
564
		printM('Logging initialized successfully.', sender=SENDER)
565
566
	printM('Using settings file: %s' % settings_loc)
567
568
	odir = os.path.abspath(os.path.expanduser(settings['settings']['output_dir']))
569
	init_dirs(odir)
570
	if debug:
571
		printM('Output directory is: %s' % odir)
572
573
	run(settings, debug=debug)
574
575
def test():
576
	'''
577
	.. versionadded:: 0.4.3
578
579
	Set up tests, run modules, report test results.
580
	For a list of tests run, see :py:mod:`rsudp.test`.
581
	'''
582
	global TESTFILE
583
	hlp_txt='''
584
###########################################
585
##     R A S P B E R R Y  S H A K E      ##
586
##            Testing Module             ##
587
##            by Ian Nesbitt             ##
588
##            GNU GPLv3 2020             ##
589
##                                       ##
590
## Test settings with archived Shake     ##
591
## data to determine optimal             ##
592
## configuration.                        ##
593
##                                       ##
594
##  Requires:                            ##
595
##  - numpy, obspy, matplotlib 3         ##
596
##                                       ##
597
###########################################
598
599
Usage: rs-test [ OPTIONS ]
600
where OPTIONS := {
601
    -h | --help
602
            display this help message
603
    -f | --file=default or /path/to/data/file
604
            specify the path to a seismic data file
605
    -s | --settings=/path/to/settings/json
606
            specify the path to a JSON-formatted settings file
607
    }
608
609
rs-test with no arguments will start the test with
610
default settings and the data file at
611
%s
612
''' % (TESTFILE)
613
614
	test_mode(True)
615
	settings = default_settings(verbose=False)
616
	settings_are_default = True
617
	plot = True
618
	quiet = False
619
620
	try:
621
		opts = getopt.getopt(sys.argv[1:], 'hf:s:bq',
622
			['help', 'file=', 'settings=', 'no-plot',]
623
			)[0]
624
	except Exception as e:
625
		print(COLOR['red'] + 'ERROR: %s' % e + COLOR['white'])
626
		print(hlp_txt)
627
		exit(1)
628
629
	for o, a in opts:
630
		if o in ('-h', '--help'):
631
			print(hlp_txt)
632
			exit(0)
633
		if o in ('-f', '--file='):
634
			'''
635
			The data file.
636
			'''
637
			a = os.path.expanduser(a)
638
			if os.path.exists(a):
639
				try:
640
					out = '%s.txt' % (a)
641
					packetize(inf=a, outf=out)
642
					TESTFILE = out
643
				except Exception as e:
644
					print(COLOR['red'] + 'ERROR: %s' % e + COLOR['white'])
645
					print(hlp_txt)
646
					exit(1)
647
		if o in ('-s', '--settings='):
648
			'''
649
			Dump the settings to a file, specified after the `-d` flag, or `-d default` to let the software decide where to put it.
650
			'''
651
			settings_loc = os.path.abspath(os.path.expanduser(a)).replace('\\', '/')
652
			if os.path.exists(settings_loc):
653
				settings = read_settings(settings_loc)
654
				settings_are_default = False
655
			else:
656
				print(COLOR['red'] + 'ERROR: could not find settings file at %s' % (a) + COLOR['white'])
657
				exit(1)
658
		if o in ('-b', '--no-plot'):
659
			plot = False
660
		if o in ('-q', '--no-sound'):
661
			quiet = True
662
663
664
	t.TEST['n_internet'][1] = t.is_connected('www.google.com')
665
666
	if settings_are_default:
667
		settings = t.make_test_settings(settings=settings, inet=t.TEST['n_internet'][1])
668
669
	t.TEST['p_log_dir'][1] = t.logdir_permissions()
670
	t.TEST['p_log_file'][1] = start_logging(testing=True)
671
	t.TEST['p_log_std'][1] = add_debug_handler(testing=True)
672
673
	t.TEST['p_output_dirs'][1] = init_dirs(os.path.expanduser(settings['settings']['output_dir']))
674
	t.TEST['p_data_dir'][1] = t.datadir_permissions(os.path.expanduser(settings['settings']['output_dir']))
675
	t.TEST['p_screenshot_dir'][1] = t.ss_permissions(os.path.expanduser(settings['settings']['output_dir']))
676
677
	if plot:
678
		if MPL:
679
			t.TEST['d_matplotlib'][1] = True
680
		else:
681
			printW('matplotlib backend failed to load')
682
	else:
683
		settings['plot']['enabled'] = False
684
		del t.TEST['d_matplotlib']
685
		del t.TEST['c_IMGPATH']
686
		printM('Plot is disabled')
687
688
	if quiet:
689
		settings['alertsound']['enabled'] = False
690
		del t.TEST['d_pydub']
691
		printM('Alert sound is disabled')
692
693
	run(settings, debug=True)
694
695
	TESTQUEUE.put(b'ENDTEST')
696
	printW('Test finished.', sender=SENDER, announce=False)
697
698
	print()
699
700
	code = 0
701
	printM('Test results:')
702
	for i in t.TEST:
703
		printM('%s: %s' % (t.TEST[i][0], t.TRANS[t.TEST[i][1]]))
704
		if not t.TEST[i][1]:
705
			# if a test fails, change the system exit code to indicate an error occurred
706
			code = 1
707
	_xit(code)
708
709
710
if __name__ == '__main__':
711
	main()
712