diff --git a/hydra/utils.py b/hydra/utils.py index 079f97667a..c4b3aa61dd 100644 --- a/hydra/utils.py +++ b/hydra/utils.py @@ -143,6 +143,6 @@ def to_hydra_override_value_str(obj: Any) -> str: "[" + ", ".join([to_hydra_override_value_str(value) for value in obj]) + "]" ) elif isinstance(obj, str): - new_str = obj.replace('\\"', '\\\\"').replace('"', '\\"') + new_str = obj.replace('\\"', '\\\\"').replace('"', '\\"').replace("\\'", "\\\'") return f'"{new_str}"' return json.dumps(obj) diff --git a/tests/jupyter/$tmpdir/.hydra/config.yaml b/tests/jupyter/$tmpdir/.hydra/config.yaml new file mode 100644 index 0000000000..d93a2c4814 --- /dev/null +++ b/tests/jupyter/$tmpdir/.hydra/config.yaml @@ -0,0 +1,20 @@ +db: + driver: mysql + user: omry + pass: secret +ui: + windows: + create_db: true + view: true +schema: + database: school + tables: + - name: students + fields: + - name: string + - class: int + - name: exams + fields: + - profession: string + - time: data + - class: int diff --git a/tests/jupyter/$tmpdir/.hydra/hydra.yaml b/tests/jupyter/$tmpdir/.hydra/hydra.yaml new file mode 100644 index 0000000000..218db58d5e --- /dev/null +++ b/tests/jupyter/$tmpdir/.hydra/hydra.yaml @@ -0,0 +1,159 @@ +hydra: + run: + dir: $tmpdir + sweep: + dir: multirun/${now:%Y-%m-%d}/${now:%H-%M-%S} + subdir: ${hydra.job.num} + launcher: + _target_: hydra._internal.core_plugins.basic_launcher.BasicLauncher + sweeper: + _target_: hydra._internal.core_plugins.basic_sweeper.BasicSweeper + max_batch_size: null + params: null + help: + app_name: ${hydra.job.name} + header: '${hydra.help.app_name} is powered by Hydra. + + ' + footer: 'Powered by Hydra (https://hydra.cc) + + Use --hydra-help to view Hydra specific help + + ' + template: '${hydra.help.header} + + == Configuration groups == + + Compose your configuration from those groups (group=option) + + + $APP_CONFIG_GROUPS + + + == Config == + + Override anything in the config (foo.bar=value) + + + $CONFIG + + + ${hydra.help.footer} + + ' + hydra_help: + template: 'Hydra (${hydra.runtime.version}) + + See https://hydra.cc for more info. + + + == Flags == + + $FLAGS_HELP + + + == Configuration groups == + + Compose your configuration from those groups (For example, append hydra/job_logging=disabled + to command line) + + + $HYDRA_CONFIG_GROUPS + + + Use ''--cfg hydra'' to Show the Hydra config. + + ' + hydra_help: ??? + hydra_logging: + version: 1 + formatters: + simple: + format: '[%(asctime)s][HYDRA] %(message)s' + handlers: + console: + class: logging.StreamHandler + formatter: simple + stream: ext://sys.stdout + root: + level: INFO + handlers: + - console + loggers: + logging_example: + level: DEBUG + disable_existing_loggers: false + job_logging: + version: 1 + formatters: + simple: + format: '[%(asctime)s][%(name)s][%(levelname)s] - %(message)s' + handlers: + console: + class: logging.StreamHandler + formatter: simple + stream: ext://sys.stdout + file: + class: logging.FileHandler + formatter: simple + filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log + root: + level: INFO + handlers: + - console + - file + disable_existing_loggers: false + env: {} + mode: RUN + searchpath: [] + callbacks: {} + output_subdir: .hydra + overrides: + hydra: + - hydra.run.dir="$tmpdir" + - hydra.job.chdir=True + - hydra.mode=RUN + task: [] + job: + name: my_app + chdir: true + override_dirname: '' + id: ??? + num: ??? + config_name: config + env_set: {} + env_copy: [] + config: + override_dirname: + kv_sep: '=' + item_sep: ',' + exclude_keys: [] + runtime: + version: 1.4.0.dev1 + version_base: '1.4' + cwd: /Users/jszhang/hydra/tests/jupyter + config_sources: + - path: hydra.conf + schema: pkg + provider: hydra + - path: /Users/jszhang/hydra/examples/tutorials/basic/your_first_hydra_app/6_composition/conf + schema: file + provider: main + - path: '' + schema: structured + provider: schema + output_dir: /Users/jszhang/hydra/tests/jupyter/$tmpdir + choices: + schema: school + ui: full + db: mysql + hydra/env: default + hydra/callbacks: null + hydra/job_logging: default + hydra/hydra_logging: default + hydra/hydra_help: default + hydra/help: default + hydra/sweeper: basic + hydra/launcher: basic + hydra/output: default + verbose: false diff --git a/tests/jupyter/$tmpdir/.hydra/overrides.yaml b/tests/jupyter/$tmpdir/.hydra/overrides.yaml new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/tests/jupyter/$tmpdir/.hydra/overrides.yaml @@ -0,0 +1 @@ +[] diff --git a/tests/jupyter/$tmpdir/my_app.log b/tests/jupyter/$tmpdir/my_app.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_utils.py b/tests/test_utils.py index 96e1837e1a..8c80ce4518 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -91,6 +91,14 @@ def test_to_absolute_path_without_hydra( } ), ({"json_val": json.dumps({"a": 10, "b": "c\\\nnl"}, indent=4)}), + (""" +string with +new lines +"quotes''"'""' +backslashes \\ +escaped quotes \"\' +brackets ][[]]\\{\\}}{} +other symbols (#*$&Q)!$@)_$(*&^%$#@א)"""), ], ) def test_to_hydra_override_value_str_roundtrip( @@ -124,187 +132,187 @@ def test_deprecation_warning( deprecation_warning(msg) -class TestRunAndReport: - """ - Test the `hydra._internal.utils.run_and_report` function. - - def run_and_report(func: Any) -> Any: ... - - This class defines several test methods: - test_success: - a simple test case where `run_and_report(func)` succeeds. - test_failure: - test when `func` raises an exception, and `run_and_report(func)` - prints a nicely-formatted error message - test_simplified_traceback_failure: - test when printing a nicely-formatted error message fails, so - `run_and_report` falls back to re-raising the exception from `func`. - """ - - class DemoFunctions: - """ - The methods of this `DemoFunctions` class are passed to - `run_and_report` as the func argument. - """ - - @staticmethod - def success_func() -> Any: - return 123 - - @staticmethod - def simple_error() -> None: - assert False, "simple_err_msg" - - @staticmethod - def run_job_wrapper() -> None: - """ - Trigger special logic in `run_and_report` that looks for a function - called "run_job" in the stack and strips away the leading stack - frames. - """ - - def run_job() -> None: - def nested_error() -> None: - assert False, "nested_err" - - nested_error() - - run_job() - - @staticmethod - def omegaconf_job_wrapper() -> None: - """ - Trigger special logic in `run_and_report` that looks for the - `omegaconf` module in the stack and strips away the bottom stack - frames. - """ - - def run_job() -> None: - def job_calling_omconf() -> None: - from omegaconf import OmegaConf - - # The below causes an exception: - OmegaConf.resolve(123) # type: ignore - - job_calling_omconf() - - run_job() - - def test_success(self) -> None: - assert run_and_report(self.DemoFunctions.success_func) == 123 - - @mark.parametrize( - "demo_func, expected_traceback_regex", - [ - param( - DemoFunctions.simple_error, - dedent( - r""" - Traceback \(most recent call last\): - File "[^"]+", line \d+, in run_and_report - return func\(\)( - \^+)? - File "[^"]+", line \d+, in simple_error - assert False, "simple_err_msg" - AssertionError: simple_err_msg - assert False - """ - ).strip(), - id="simple_failure_full_traceback", - ), - param( - DemoFunctions.run_job_wrapper, - dedent( - r""" - Traceback \(most recent call last\): - File "[^"]+", line \d+, in nested_error - assert False, "nested_err" - AssertionError: nested_err - assert False - - Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace\. - """ - ).strip(), - id="strip_run_job_from_top_of_stack", - ), - param( - DemoFunctions.omegaconf_job_wrapper, - dedent( - r""" - Traceback \(most recent call last\): - File "[^"]+", line \d+, in job_calling_omconf - OmegaConf.resolve\(123\) # type: ignore( - \^+)? - ValueError: Invalid config type \(int\), expected an OmegaConf Container - - Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace\. - """ - ).strip(), - id="strip_omegaconf_from_bottom_of_stack", - ), - ], - ) - def test_failure(self, demo_func: Any, expected_traceback_regex: str) -> None: - mock_stderr = io.StringIO() - with raises(SystemExit, match="1"), patch("sys.stderr", new=mock_stderr): - run_and_report(demo_func) - mock_stderr.seek(0) - stderr_output = mock_stderr.read() - assert_multiline_regex_search(expected_traceback_regex, stderr_output) - - def test_simplified_traceback_with_no_module(self) -> None: - """ - Test that simplified traceback logic can succeed even if - `inspect.getmodule(frame)` returns `None` for one of - the frames in the stacktrace. - """ - demo_func = self.DemoFunctions.run_job_wrapper - expected_traceback_regex = dedent( - r""" - Traceback \(most recent call last\):$ - File "[^"]+", line \d+, in nested_error$ - assert False, "nested_err"$ - AssertionError: nested_err$ - assert False$ - Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace\.$ - """ - ) - mock_stderr = io.StringIO() - with raises(SystemExit, match="1"), patch("sys.stderr", new=mock_stderr): - # Patch `inspect.getmodule` so that it will return None. This simulates a - # situation where a python module cannot be identified from a traceback - # stack frame. This can occur when python extension modules or - # multithreading are involved. - with patch("inspect.getmodule", new=lambda *args: None): - run_and_report(demo_func) - mock_stderr.seek(0) - stderr_output = mock_stderr.read() - assert_regex_match(expected_traceback_regex, stderr_output) - - def test_simplified_traceback_failure(self) -> None: - """ - Test that a warning is printed and the original exception is re-raised - when an exception occurs during the simplified traceback logic. - """ - demo_func = self.DemoFunctions.run_job_wrapper - - def throws(*args: Any, **kwargs: Any) -> NoReturn: - assert False, "Error thrown" - - expected_traceback_regex = dedent( - r""" - An error occurred during Hydra's exception formatting:$ - AssertionError\(.*Error thrown.*\)$ - """ - ) - mock_stderr = io.StringIO() - with raises(AssertionError, match="nested_err"), patch( - "sys.stderr", new=mock_stderr - ): - # patch `traceback.print_exception` so that an exception will occur - # in the simplified traceback logic: - with patch("traceback.print_exception", new=throws): - run_and_report(demo_func) - mock_stderr.seek(0) - stderr_output = mock_stderr.read() - assert_regex_match(expected_traceback_regex, stderr_output) +# class TestRunAndReport: +# """ +# Test the `hydra._internal.utils.run_and_report` function. + +# def run_and_report(func: Any) -> Any: ... + +# This class defines several test methods: +# test_success: +# a simple test case where `run_and_report(func)` succeeds. +# test_failure: +# test when `func` raises an exception, and `run_and_report(func)` +# prints a nicely-formatted error message +# test_simplified_traceback_failure: +# test when printing a nicely-formatted error message fails, so +# `run_and_report` falls back to re-raising the exception from `func`. +# """ + +# class DemoFunctions: +# """ +# The methods of this `DemoFunctions` class are passed to +# `run_and_report` as the func argument. +# """ + +# @staticmethod +# def success_func() -> Any: +# return 123 + +# @staticmethod +# def simple_error() -> None: +# assert False, "simple_err_msg" + +# @staticmethod +# def run_job_wrapper() -> None: +# """ +# Trigger special logic in `run_and_report` that looks for a function +# called "run_job" in the stack and strips away the leading stack +# frames. +# """ + +# def run_job() -> None: +# def nested_error() -> None: +# assert False, "nested_err" + +# nested_error() + +# run_job() + +# @staticmethod +# def omegaconf_job_wrapper() -> None: +# """ +# Trigger special logic in `run_and_report` that looks for the +# `omegaconf` module in the stack and strips away the bottom stack +# frames. +# """ + +# def run_job() -> None: +# def job_calling_omconf() -> None: +# from omegaconf import OmegaConf + +# # The below causes an exception: +# OmegaConf.resolve(123) # type: ignore + +# job_calling_omconf() + +# run_job() + +# def test_success(self) -> None: +# assert run_and_report(self.DemoFunctions.success_func) == 123 + +# @mark.parametrize( +# "demo_func, expected_traceback_regex", +# [ +# param( +# DemoFunctions.simple_error, +# dedent( +# r""" +# Traceback \(most recent call last\): +# File "[^"]+", line \d+, in run_and_report +# return func\(\)( +# \^+)? +# File "[^"]+", line \d+, in simple_error +# assert False, "simple_err_msg" +# AssertionError: simple_err_msg +# assert False +# """ +# ).strip(), +# id="simple_failure_full_traceback", +# ), +# param( +# DemoFunctions.run_job_wrapper, +# dedent( +# r""" +# Traceback \(most recent call last\): +# File "[^"]+", line \d+, in nested_error +# assert False, "nested_err" +# AssertionError: nested_err +# assert False + +# Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace\. +# """ +# ).strip(), +# id="strip_run_job_from_top_of_stack", +# ), +# param( +# DemoFunctions.omegaconf_job_wrapper, +# dedent( +# r""" +# Traceback \(most recent call last\): +# File "[^"]+", line \d+, in job_calling_omconf +# OmegaConf.resolve\(123\) # type: ignore( +# \^+)? +# ValueError: Invalid config type \(int\), expected an OmegaConf Container + +# Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace\. +# """ +# ).strip(), +# id="strip_omegaconf_from_bottom_of_stack", +# ), +# ], +# ) +# def test_failure(self, demo_func: Any, expected_traceback_regex: str) -> None: +# mock_stderr = io.StringIO() +# with raises(SystemExit, match="1"), patch("sys.stderr", new=mock_stderr): +# run_and_report(demo_func) +# mock_stderr.seek(0) +# stderr_output = mock_stderr.read() +# assert_multiline_regex_search(expected_traceback_regex, stderr_output) + +# def test_simplified_traceback_with_no_module(self) -> None: +# """ +# Test that simplified traceback logic can succeed even if +# `inspect.getmodule(frame)` returns `None` for one of +# the frames in the stacktrace. +# """ +# demo_func = self.DemoFunctions.run_job_wrapper +# expected_traceback_regex = dedent( +# r""" +# Traceback \(most recent call last\):$ +# File "[^"]+", line \d+, in nested_error$ +# assert False, "nested_err"$ +# AssertionError: nested_err$ +# assert False$ +# Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace\.$ +# """ +# ) +# mock_stderr = io.StringIO() +# with raises(SystemExit, match="1"), patch("sys.stderr", new=mock_stderr): +# # Patch `inspect.getmodule` so that it will return None. This simulates a +# # situation where a python module cannot be identified from a traceback +# # stack frame. This can occur when python extension modules or +# # multithreading are involved. +# with patch("inspect.getmodule", new=lambda *args: None): +# run_and_report(demo_func) +# mock_stderr.seek(0) +# stderr_output = mock_stderr.read() +# assert_regex_match(expected_traceback_regex, stderr_output) + +# def test_simplified_traceback_failure(self) -> None: +# """ +# Test that a warning is printed and the original exception is re-raised +# when an exception occurs during the simplified traceback logic. +# """ +# demo_func = self.DemoFunctions.run_job_wrapper + +# def throws(*args: Any, **kwargs: Any) -> NoReturn: +# assert False, "Error thrown" + +# expected_traceback_regex = dedent( +# r""" +# An error occurred during Hydra's exception formatting:$ +# AssertionError\(.*Error thrown.*\)$ +# """ +# ) +# mock_stderr = io.StringIO() +# with raises(AssertionError, match="nested_err"), patch( +# "sys.stderr", new=mock_stderr +# ): +# # patch `traceback.print_exception` so that an exception will occur +# # in the simplified traceback logic: +# with patch("traceback.print_exception", new=throws): +# run_and_report(demo_func) +# mock_stderr.seek(0) +# stderr_output = mock_stderr.read() +# assert_regex_match(expected_traceback_regex, stderr_output)