Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: I/O failures causing a crash + unify logging and assertions #65

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
5 changes: 1 addition & 4 deletions ue4cli/JsonDataManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ def getDictionary(self):
Retrieves the entire data dictionary
"""
if os.path.exists(self.jsonFile):
try:
return json.loads(Utility.readFile(self.jsonFile))
except json.JSONDecodeError as err:
raise UnrealManagerException('malformed JSON configuration file "{}" ({})'.format(self.jsonFile, err))
return json.loads(Utility.readFile(self.jsonFile))
else:
return {}

Expand Down
16 changes: 9 additions & 7 deletions ue4cli/UnrealManagerBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ def setEngineRootOverride(self, rootDir):
# Set the new root directory
rootDir = os.path.abspath(rootDir)
ConfigurationManager.setConfigKey('rootDirOverride', rootDir)
print('Set engine root path override: {}'.format(rootDir))
Utility.printStderr('Setting engine root path override:', str(rootDir))

# Check that the specified directory is valid and warn the user if it is not
try:
self.getEngineVersion()
except:
print('Warning: the specified directory does not appear to contain a valid version of the Unreal Engine.')
UnrealManagerException('the specified directory does not appear to contain a valid version of the Unreal Engine.')
sleeptightAnsiC marked this conversation as resolved.
Show resolved Hide resolved

def clearEngineRootOverride(self):
"""
Expand Down Expand Up @@ -94,7 +94,7 @@ def getEngineVersion(self, outputFormat = 'full'):

# Verify that the requested output format is valid
if outputFormat not in formats:
raise Exception('unreconised version output format "{}"'.format(outputFormat))
raise UnrealManagerException('unreconised version output format "{}"'.format(outputFormat))

return formats[outputFormat]

Expand Down Expand Up @@ -538,7 +538,7 @@ def listAutomationTests(self, projectFile):
# Detect if the Editor terminated abnormally (i.e. not triggered by `automation quit`)
# In Unreal Engine 4.27.0, the exit method changed from RequestExit to RequestExitWithStatus
if 'PlatformMisc::RequestExit(' not in logOutput.stdout and 'PlatformMisc::RequestExitWithStatus(' not in logOutput.stdout:
raise RuntimeError(
raise UnrealManagerException(
'failed to retrieve the list of automation tests!' +
' stdout was: "{}", stderr was: "{}"'.format(logOutput.stdout, logOutput.stderr)
)
Expand All @@ -556,7 +556,7 @@ def automationTests(self, dir=os.getcwd(), args=[]):

# Verify that at least one argument was supplied
if len(args) == 0:
raise RuntimeError('at least one test name must be specified')
raise UnrealManagerException('at least one test name must be specified')

# Gather any additional arguments to pass directly to the Editor
extraArgs = []
Expand Down Expand Up @@ -605,7 +605,7 @@ def automationTests(self, dir=os.getcwd(), args=[]):
# Detect abnormal exit conditions (those not triggered by `automation quit`)
# In Unreal Engine 4.27.0, the exit method changed from RequestExit to RequestExitWithStatus
if 'PlatformMisc::RequestExit(' not in logOutput.stdout and 'PlatformMisc::RequestExitWithStatus(' not in logOutput.stdout:
sys.exit(1)
raise UnrealManagerException('abnormal exit condition')

# Since UE4 doesn't consistently produce accurate exit codes across all platforms, we need to rely on
# text-based heuristics to detect failed automation tests or errors related to not finding any tests to run
Expand All @@ -618,12 +618,14 @@ def automationTests(self, dir=os.getcwd(), args=[]):
]
for errorStr in errorStrings:
if errorStr in logOutput.stdout:
sys.exit(1)
raise UnrealManagerException('abnormal exit condition')

# If an explicit exit code was specified in the output text then identify it and propagate it
match = re.search('TEST COMPLETE\\. EXIT CODE: ([0-9]+)', logOutput.stdout + logOutput.stderr)
if match is not None:
sys.exit(int(match.group(1)))
else:
raise UnrealManagerException('abnormal exit condition')

# "Protected" methods

Expand Down
2 changes: 1 addition & 1 deletion ue4cli/UnrealManagerWindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def generateProjectFiles(self, dir=os.getcwd(), args=[]):
# If we are using our custom batch file, use the appropriate arguments
genScript = self.getGenerateScript()
projectFile = self.getProjectDescriptor(dir)
print(projectFile)
Utility.printStderr('Using project file:', projectFile)
if '.ue4\\GenerateProjectFiles.bat' in genScript:
Utility.run([genScript, projectFile], raiseOnError=True)
else:
Expand Down
8 changes: 4 additions & 4 deletions ue4cli/Utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def printStderr(*args, **kwargs):
Prints to stderr instead of stdout
"""
if os.environ.get('UE4CLI_QUIET', '0') != '1':
print(*args, file=sys.stderr, **kwargs)
print('(ue4cli)', *args, end='\n', file=sys.stderr, **kwargs)

@staticmethod
def readFile(filename):
Expand Down Expand Up @@ -123,7 +123,7 @@ def capture(command, input=None, cwd=None, shell=False, raiseOnError=False):

# If the child process failed and we were asked to raise an exception, do so
if raiseOnError == True and proc.returncode != 0:
raise Exception(
raise subprocess.SubprocessError(
'child process ' + str(command) +
' failed with exit code ' + str(proc.returncode) +
'\nstdout: "' + stdout + '"' +
Expand All @@ -143,7 +143,7 @@ def run(command, cwd=None, shell=False, raiseOnError=False):

returncode = subprocess.call(command, cwd=cwd, shell=shell)
if raiseOnError == True and returncode != 0:
raise Exception('child process ' + str(command) + ' failed with exit code ' + str(returncode))
raise subprocess.SubprocessError('child process ' + str(command) + ' failed with exit code ' + str(returncode))
return returncode

@staticmethod
Expand All @@ -152,4 +152,4 @@ def _printCommand(command):
Prints a command if verbose output is enabled
"""
if os.environ.get('UE4CLI_VERBOSE', '0') == '1':
Utility.printStderr('[UE4CLI] EXECUTE COMMAND:', command)
Utility.printStderr('EXECUTE COMMAND:', command)
25 changes: 21 additions & 4 deletions ue4cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
from .PluginManager import PluginManager
from .UnrealManagerException import UnrealManagerException
from .UnrealManagerFactory import UnrealManagerFactory
import os, sys
from .Utility import Utility
from subprocess import SubprocessError
from json import JSONDecodeError
import os, sys, logging

# Our list of supported commands
SUPPORTED_COMMANDS = {
Expand Down Expand Up @@ -201,6 +204,7 @@ def displayHelp():

def main():
try:
logger = logging.getLogger(__name__)

# Perform plugin detection and register our detected plugins
plugins = PluginManager.getPlugins()
Expand All @@ -222,7 +226,20 @@ def main():
SUPPORTED_COMMANDS[command]['action'](manager, args)
else:
raise UnrealManagerException('unrecognised command "' + command + '"')

except UnrealManagerException as e:
print('Error: ' + str(e))
except (
UnrealManagerException,
OSError,
SubprocessError,
JSONDecodeError,
KeyboardInterrupt,
) as e:
Utility.printStderr('(' + type(e).__name__ + ')', str(e))
sys.exit(1)
except BaseException as e:
Utility.printStderr('Unhandled exception! Crashing...')
logging.basicConfig(level=logging.DEBUG)
logger.exception(e)
Utility.printStderr('ue4cli has crashed! Please, report it at: https://github.com/adamrehn/ue4cli/issues')
sys.exit(1)