-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtestScript.py
More file actions
232 lines (197 loc) · 9.39 KB
/
Copy pathtestScript.py
File metadata and controls
232 lines (197 loc) · 9.39 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env python3
# Place for my python testing
import sys
import time
import traceback
import platform
import shutil
import uuid
import runpy
import argparse
from datetime import datetime
# If you don't have these packages, please don't run.
try:
from halo import Halo
from colorama import Fore, Style, init
except ImportError as e:
print("You don't have all the right packages or some packages are not compatible with your system: ", e)
sys.exit(1)
# Initialise Colorama
init(autoreset=True)
# -----------------------
# Variable Definitions
# -----------------------
PROJECT_TITLE = "Python Test Environment"
VERSION = "1.2"
subtitle = PROJECT_TITLE + " v." + VERSION
WIDTH = shutil.get_terminal_size(fallback=(100, 24)).columns
centerspacing = max(round((WIDTH - len(subtitle)) / 2), 0)
spinner = Halo(text="Running Python snippet...", spinner="bouncingBar", interval=-2)
errors = 0
ASCII_ART = [
"██████╗ ██╗ ██╗████████╗██╗ ██╗ ██████╗ ███╗ ██╗ ████████╗███████╗███████╗████████╗",
"██╔══██╗╚██╗ ██╔╝╚══██╔══╝██║ ██║██╔═══██╗████╗ ██║ ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝",
"██████╔╝ ╚████╔╝ ██║ ███████║██║ ██║██╔██╗ ██║ ██║ █████╗ ███████╗ ██║",
"██╔═══╝ ╚██╔╝ ██║ ██╔══██║██║ ██║██║╚██╗██║ ██║ ██╔══╝ ╚════██║ ██║",
"██║ ██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ███████╗███████║ ██║",
"╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝",
]
ERROR_QUOTES = {
ModuleNotFoundError: ("Dude, you forgot to install the module with pip."),
FileNotFoundError: ("Either the file moved, or it never existed in the first place."),
PermissionError: ("The OS said 'you shall not pass.' Check your permissions."),
IsADirectoryError: ("That's a directory, not a file, genius."),
NotADirectoryError: ("That's not a directory, so it can't be listed like one."),
FileExistsError: ("That file already exists, and it's not going anywhere."),
ZeroDivisionError: ("Even Python can't divide by absolutely nothing."),
OverflowError: ("That number got too big for its britches."),
FloatingPointError: ("The floating point math itself gave up."),
ArithmeticError: ("The math broke somewhere along the way."),
IndexError: ("That index is beyond the end of the list."),
KeyError: ("That dictionary key doesn't exist."),
LookupError: ("Python couldn't look that up."),
NameError: ("Python has no idea what that variable is."),
UnboundLocalError: ("You used a local variable before ever assigning it."),
AttributeError: ("That object doesn't have the attribute you're asking for."),
TypeError: ("Those data types don't play nicely together."),
ValueError: ("Right type, wrong value."),
UnicodeDecodeError: ("Python choked trying to decode those bytes."),
UnicodeEncodeError: ("Python choked trying to encode that string."),
ImportError: ("Python couldn't import what you requested."),
SyntaxError: ("Python couldn't even finish reading your code."),
IndentationError: ("Python is judging your whitespace."),
TabError: ("Pick spaces or tabs and stick with it."),
RecursionError: ("You've gone a little too deep..."),
MemoryError: ("Congratulations, you ran out of RAM, in this economy..."),
AssertionError: ("Your assertion didn't hold up under pressure."),
StopIteration: ("You ran out of items to iterate over."),
NotImplementedError: ("Whoever wrote this never finished the job."),
ConnectionError: ("The connection didn't want to cooperate."),
TimeoutError: ("It took too long and gave up waiting."),
BrokenPipeError: ("The pipe broke somewhere along the way."),
EOFError: ("Python hit the end of the input when it expected more."),
OSError: ("The operating system said no."),
KeyboardInterrupt: ("Execution interrupted by user."),
Exception: ("Something unexpected happened.")
}
# -----------------------
# Argument Parsing
# -----------------------
def parseArgs() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="testScript.py",
description="Python Test Environment - safely run a snippet or an external .py file with error handling."
)
parser.add_argument(
"script",
nargs="?",
default=None,
help="Path to a .py file to run. If omitted, runs the inline code in the try block instead."
)
parser.add_argument(
"-a", "--args",
nargs=argparse.REMAINDER,
default=[],
metavar="...",
help="Everything after this flag is passed through to the target script as sys.argv."
)
return parser.parse_args()
args = parseArgs()
EXTERNAL_SCRIPT = args.script # set via `python testScript.py path/to/script.py`
# -----------------------
# Functions
# -----------------------
# Helper: Prints out the logo, centered to the terminal width
def printTitle():
art_width = max(len(line) for line in ASCII_ART)
art_padding = " " * max((WIDTH - art_width) // 2, 0)
for line in ASCII_ART:
print(Fore.LIGHTCYAN_EX + art_padding + line)
print(Fore.CYAN + Style.BRIGHT + " " * centerspacing + subtitle)
print(Fore.LIGHTYELLOW_EX + "=" * WIDTH)
print(Fore.LIGHTYELLOW_EX + "Your code can be safe here. All errors are handled!")
print(Fore.LIGHTYELLOW_EX + "=" * WIDTH)
print()
# Helper: Prints out a string using Halo before resuming the spinner
def spinnerPrint(string: str, delay: float = 0.5) -> None:
spinner.info(string)
time.sleep(delay)
spinner.start()
# Helper: Looks up the most specific matching quote by walking the
# exception's MRO, so subclasses of a known exception still get a
# sensible message instead of always falling back to the generic one.
def getErrorQuote(exc: Exception) -> str:
for exc_type in type(exc).__mro__:
if exc_type in ERROR_QUOTES:
return ERROR_QUOTES[exc_type]
return ERROR_QUOTES[Exception]
printTitle()
start_time = time.perf_counter()
spinner.start()
# -----------------------
# Code
# -----------------------
try:
if EXTERNAL_SCRIPT:
# Runs the target file as if it were __main__; any exception it
# raises propagates up and is caught below, same as inline code.
# e.g. python testScript.py myscript.py -a --flag value
original_argv = sys.argv
sys.argv = [EXTERNAL_SCRIPT] + args.args
try:
runpy.run_path(EXTERNAL_SCRIPT, run_name="__main__")
finally:
sys.argv = original_argv
else:
# Your code will be safe here
pass
except KeyboardInterrupt as e:
spinner.fail(Fore.RED + "Execution Interrupted")
errors += 1
runtime = time.perf_counter() - start_time
print()
print(Fore.YELLOW + Style.BRIGHT + ERROR_QUOTES[KeyboardInterrupt])
except Exception as e:
spinner.fail(Fore.RED + "Execution Failed")
errors += 1
runtime = time.perf_counter() - start_time
crash_id = str(uuid.uuid4())[:8].upper()
error_quote = getErrorQuote(e)
print()
print(Fore.RED + "=" * WIDTH)
print(Fore.RED + Style.BRIGHT + (" "*max(round((WIDTH - 36) / 2), 0)) + "Python Test Environment Crash Report" + (" "*max(round((WIDTH - 36) / 2), 0)))
print(Fore.RED + "=" * WIDTH)
print(Fore.CYAN + f"Crash ID : {crash_id}")
print(Fore.CYAN + f"Time : {datetime.now():%Y-%m-%d %H:%M:%S}")
print(Fore.CYAN + f"Runtime : {runtime:.4f} seconds")
print()
print(Fore.YELLOW + Style.BRIGHT + "Environment")
print(Fore.WHITE + f" Python : {platform.python_version()}")
print(Fore.WHITE + f" Platform : {platform.system()} {platform.release()}")
print(Fore.WHITE + f" Machine : {platform.machine()}")
print()
print(Fore.YELLOW + Style.BRIGHT + "Exception")
print(Fore.RED + Style.BRIGHT + f" Type : {type(e).__name__}")
print(Fore.WHITE + f" Message : {e}")
print(Fore.BLUE + Style.BRIGHT + f" {error_quote}")
print()
print(Fore.YELLOW + "-" * WIDTH)
print(Fore.YELLOW + "Full Traceback")
print(Fore.YELLOW + "-" * WIDTH)
traceback.print_exc()
print()
finally:
runtime = time.perf_counter() - start_time
if errors == 0:
spinner.succeed(Fore.GREEN + "Execution Completed")
print()
print(Fore.GREEN + "=" * WIDTH)
print(Fore.GREEN + Style.BRIGHT +
"✔ Nice Job with no errors whatsoever!")
else:
print(Fore.RED + "=" * WIDTH)
print(Fore.RED + f"✖ Your code ran with {errors} error(s).")
print(Fore.CYAN + f"Execution Time : {runtime:.4f} seconds")
print(Fore.CYAN + f"Errors : {errors}")
if errors: sys.exit(1)
else: sys.exit(0)