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

Logger cleanup and clarification #75

Merged
merged 4 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/tutorials/dev_add_test_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -678,13 +678,13 @@ $ cat polaris.o${SLURM_JOBID}
Done.

ocean/yet_another_channel/10km/default
compass calling: polaris.run.serial._run_test()
polaris calling: polaris.run.serial._run_test()
in /gpfs/fs1/home/ac.cbegeman/polaris-repo/main/polaris/run/serial.py

Running steps: initial_state
* step: initial_state

compass calling: polaris.ocean.tests.yet_another_channel.default.Default.validate()
polaris calling: polaris.ocean.tests.yet_another_channel.default.Default.validate()
inherited from: polaris.testcase.TestCase.validate()
in /gpfs/fs1/home/ac.cbegeman/polaris-repo/main/polaris/testcase.py

Expand Down
14 changes: 7 additions & 7 deletions polaris/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ def log_method_call(method, logger):
"""
Log the module path and file path of a call to a method, e.g.::

compass calling: compass.landice.tests.dome.decomposition_test.DecompositionTest.run() # noqa: E501
in /turquoise/usr/projects/climate/mhoffman/mpas/compass/compass/landice/tests/dome/decomposition_test/__init__.py # noqa: E501
polaris calling: polaris.landice.tests.dome.decomposition_test.DecompositionTest.run() # noqa: E501
in /turquoise/usr/projects/climate/mhoffman/mpas/polaris/polaris/landice/tests/dome/decomposition_test/__init__.py # noqa: E501

Parameters
----------
Expand Down Expand Up @@ -49,14 +49,14 @@ def log_method_call(method, logger):
actual_location = f'{actual_class.__module__}.{actual_class.__name__}'

# not every class is defined in a file (e.g. python intrinsics) but we
# expect they always are in compass. Still we'll check to make sure.
# expect they always are in polaris. Still we'll check to make sure.
try:
class_file = inspect.getfile(actual_class)
except TypeError:
class_file = None

# log what we found out
logger.info(f'compass calling: {child_location}.{method_name}()')
logger.info(f'polaris calling: {child_location}.{method_name}()')
if child_location != actual_location:
# okay, so we're inheriting this method from somewhere else. Better
# let the user know.
Expand All @@ -69,8 +69,8 @@ def log_function_call(function, logger):
"""
Log the module path and file path of a call to a function, e.g.::

compass calling: compass.parallel.set_cores_per_node()
in /home/xylar/code/compass/compass/compass/parallel.py
polaris calling: polaris.parallel.set_cores_per_node()
in /home/xylar/code/polaris/polaris/polaris/parallel.py

Parameters
----------
Expand All @@ -86,6 +86,6 @@ def log_function_call(function, logger):
filename = inspect.getfile(function)

# log what we found out
logger.info(f'compass calling: '
logger.info(f'polaris calling: '
f'{function.__module__}.{function.__name__}()')
logger.info(f' in {filename}')
70 changes: 42 additions & 28 deletions polaris/run/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def run_tests(suite_name, quiet=False, is_test_case=False, steps_to_run=None,
check_parallel_system(config)

# start logging to stdout/stderr
with LoggingContext(suite_name) as logger:
with LoggingContext(suite_name) as stdout_logger:

os.environ['PYTHONUNBUFFERED'] = '1'

Expand All @@ -81,20 +81,20 @@ def run_tests(suite_name, quiet=False, is_test_case=False, steps_to_run=None,
test_times = dict()
success_strs = dict()
for test_name in test_suite['test_cases']:
logger.info(f'{test_name}')
stdout_logger.info(f'{test_name}')

test_case = test_suite['test_cases'][test_name]

if is_test_case:
log_filename = None
test_logger = logger
test_logger = stdout_logger
else:
test_prefix = test_case.path.replace('/', '_')
log_filename = f'{cwd}/case_outputs/{test_prefix}.log'
test_logger = None

success_str, success, test_time = _log_and_run_test(
test_case, logger, test_logger, quiet, log_filename,
test_case, stdout_logger, test_logger, quiet, log_filename,
is_test_case, steps_to_run, steps_to_skip)
success_strs[test_name] = success_str
if not success:
Expand All @@ -105,26 +105,26 @@ def run_tests(suite_name, quiet=False, is_test_case=False, steps_to_run=None,

os.chdir(cwd)

logger.info('Test Runtimes:')
stdout_logger.info('Test Runtimes:')
for test_name, test_time in test_times.items():
secs = round(test_time)
mins = secs // 60
secs -= 60 * mins
logger.info(f'{mins:02d}:{secs:02d} {success_strs[test_name]} '
f'{test_name}')
stdout_logger.info(f'{mins:02d}:{secs:02d} \
{success_strs[test_name]} {test_name}')
secs = round(suite_time)
mins = secs // 60
secs -= 60 * mins
logger.info(f'Total runtime {mins:02d}:{secs:02d}')
stdout_logger.info(f'Total runtime {mins:02d}:{secs:02d}')

if failures == 0:
logger.info('PASS: All passed successfully!')
stdout_logger.info('PASS: All passed successfully!')
else:
if failures == 1:
message = '1 test'
else:
message = f'{failures} tests'
logger.error(f'FAIL: {message} failed, see above.')
stdout_logger.error(f'FAIL: {message} failed, see above.')
sys.exit(1)


Expand Down Expand Up @@ -159,19 +159,19 @@ def run_single_step(step_is_subprocess=False):

# start logging to stdout/stderr
test_name = step.path.replace('/', '_')
with LoggingContext(name=test_name) as logger:
test_case.logger = logger
with LoggingContext(name=test_name) as stdout_logger:
test_case.logger = stdout_logger
test_case.stdout_logger = None
log_function_call(function=_run_test, logger=logger)
logger.info('')
log_function_call(function=_run_test, logger=stdout_logger)
stdout_logger.info('')
_run_test(test_case)

if not step_is_subprocess:
# only perform validation if the step is being run by a user on its
# own
logger.info('')
log_method_call(method=test_case.validate, logger=logger)
logger.info('')
stdout_logger.info('')
log_method_call(method=test_case.validate, logger=stdout_logger)
stdout_logger.info('')
test_case.validate()


Expand Down Expand Up @@ -257,8 +257,9 @@ def _print_to_stdout(test_case, message):
test_case.logger.info(message)


def _log_and_run_test(test_case, logger, test_logger, quiet, log_filename,
is_test_case, steps_to_run, steps_to_skip):
def _log_and_run_test(test_case, stdout_logger, test_logger, quiet,
log_filename, is_test_case, steps_to_run,
steps_to_skip):
# ANSI fail text: https://stackoverflow.com/a/287944/7728169
start_fail = '\033[91m'
start_pass = '\033[92m'
Expand All @@ -278,9 +279,13 @@ def _log_and_run_test(test_case, logger, test_logger, quiet, log_filename,
test_case.stdout_logger = test_logger
else:
# log steps to stdout
test_case.stdout_logger = logger
test_case.stdout_logger = stdout_logger
test_case.logger = test_logger
test_case.log_filename = log_filename

# If we are running a test case on its own, we want a log file per step
# If we are running within a suite, we want a log file per test, with
# output from each of its steps
test_case.new_step_log_file = is_test_case

os.chdir(test_case.work_dir)
Expand Down Expand Up @@ -357,13 +362,13 @@ def _log_and_run_test(test_case, logger, test_logger, quiet, log_filename,
f' baseline comparison: {baseline_status}'

if test_pass:
logger.info(status)
stdout_logger.info(status)
success_str = pass_str
success = True
else:
logger.error(status)
stdout_logger.error(status)
if not is_test_case:
logger.error(f' see: case_outputs/{test_name}.log')
stdout_logger.error(f' see: case_outputs/{test_name}.log')
success_str = fail_str
success = False

Expand All @@ -372,8 +377,8 @@ def _log_and_run_test(test_case, logger, test_logger, quiet, log_filename,
secs = round(test_time)
mins = secs // 60
secs -= 60 * mins
logger.info(f' test runtime: '
f'{start_time_color}{mins:02d}:{secs:02d}{end}')
stdout_logger.info(f' test runtime: '
f'{start_time_color}{mins:02d}:{secs:02d}{end}')

return success_str, success, test_time

Expand All @@ -392,6 +397,8 @@ def _run_test(test_case):
step.config = test_case.config
if test_case.log_filename is not None:
step.log_filename = test_case.log_filename
else:
step.log_filename = None

_print_to_stdout(test_case, f' * step: {step_name}')

Expand Down Expand Up @@ -429,15 +436,18 @@ def _run_step(test_case, step, new_log_file):

_load_dependencies(test_case, step)

test_name = step.path.replace('/', '_')
# each logger needs a unique name
logger_name = step.path.replace('/', '_')
if new_log_file:
log_filename = f'{cwd}/{step.name}.log'
step.log_filename = log_filename
step_logger = None
else:
step_logger = logger
log_filename = None
with LoggingContext(name=test_name, logger=step_logger,

step.log_filename = log_filename

with LoggingContext(name=logger_name, logger=step_logger,
log_filename=log_filename) as step_logger:
step.logger = step_logger
os.chdir(step.work_dir)
Expand All @@ -456,6 +466,10 @@ def _run_step(test_case, step, new_log_file):
step.runtime_setup()

if step.args is not None:
step_logger.info('\nBypassing step\'s run() method and running '
'with command line args\n')
log_function_call(function=run_command, logger=step_logger)
step_logger.info('')
run_command(step.args, step.cpus_per_task, step.ntasks,
step.openmp_threads, step.config, step.logger)
else:
Expand Down
2 changes: 1 addition & 1 deletion polaris/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def __init__(self, test_case, name, subdir=None, cpus_per_task=1,

# these will be set before running the step, dummy placeholders for now
self.logger = logging.getLogger('dummy')
self.log_filename = ""
self.log_filename = None

# output caching
self.cached = cached
Expand Down