diff --git a/.gitignore b/.gitignore index 0e4e3f615f..cdc3b6490a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ __pycache__ -.venv +.venv* *.egg-info config.ini credentials diff --git a/src/snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py b/src/snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py index 80d4cc1531..e28a401e6d 100644 --- a/src/snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +++ b/src/snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py @@ -200,7 +200,9 @@ def process( collected_output = [] collected_sql_files: List[Path] = [] - for py_file, extension_fns in collected_extension_functions_by_path.items(): + for py_file, extension_fns in sorted( + collected_extension_functions_by_path.items() + ): sql_file = self.generate_new_sql_file_name( py_file=py_file, ) @@ -326,8 +328,12 @@ def collect_extension_functions( Path, List[NativeAppExtensionFunction] ] = {} - for src_file, dest_file in bundle_map.all_mappings( - absolute=True, expand_directories=True, predicate=_is_python_file_artifact + for src_file, dest_file in sorted( + bundle_map.all_mappings( + absolute=True, + expand_directories=True, + predicate=_is_python_file_artifact, + ) ): cc.step( "Processing Snowpark annotations from {}".format( diff --git a/tests/api/test_sanitizers.py b/tests/api/test_sanitizers.py index 3249060441..2d7ae72027 100644 --- a/tests/api/test_sanitizers.py +++ b/tests/api/test_sanitizers.py @@ -17,11 +17,6 @@ from snowflake.cli.api.sanitizers import sanitize_for_terminal from typer.testing import CliRunner -from tests_common import IS_WINDOWS - -if IS_WINDOWS: - pytest.skip("Does not work on Windows", allow_module_level=True) - @pytest.mark.parametrize( "text, expected", diff --git a/tests/api/test_secure_path.py b/tests/api/test_secure_path.py index 1dca71f55e..418c96b148 100644 --- a/tests/api/test_secure_path.py +++ b/tests/api/test_secure_path.py @@ -11,20 +11,22 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - import os import re import shutil import stat +from functools import reduce from pathlib import Path import pytest +import tomlkit from snowflake.cli.api import secure_path from snowflake.cli.api.config import config_init from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError from snowflake.cli.api.secure_path import SecurePath from snowflake.cli.app import loggers +from tests.conftest import clean_logging_handlers from tests.testing_utils.files_and_dirs import assert_file_permissions_are_strict from tests_common import IS_WINDOWS @@ -36,14 +38,21 @@ def save_logs(snowflake_home): config = snowflake_home / "config.toml" logs_path = snowflake_home / "logs" - config.write_text( - "\n".join(["[cli.logs]", "save_logs = true", f'path = "{logs_path}"']) + config_data = dict( + cli=dict( + logs=dict( + save_logs=True, + path=str(logs_path), + ) + ) ) + tomlkit.dump(config_data, config.open("w")) config_init(config) loggers.create_loggers(False, False) yield logs_path + clean_logging_handlers() shutil.rmtree(logs_path) @@ -159,7 +168,8 @@ def test_open_read(temp_dir, save_logs): def test_navigation(): p = SecurePath("a/b/c") - assert str(p / "b" / "c" / "d" / "e") == 'SecurePath("a/b/c/b/c/d/e")' + pathstring = reduce(os.path.join, ["a", "b", "c", "b", "c", "d", "e"]) + assert str(p / "b" / "c" / "d" / "e") == f'SecurePath("{pathstring}")' assert str(p.parent.parent) == 'SecurePath("a")' assert type(p.absolute()) is SecurePath diff --git a/tests/nativeapp/codegen/snowpark/__snapshots__/test_python_processor.ambr b/tests/nativeapp/codegen/snowpark/__snapshots__/test_python_processor.ambr index 7f20d5b8d3..e0434f998a 100644 --- a/tests/nativeapp/codegen/snowpark/__snapshots__/test_python_processor.ambr +++ b/tests/nativeapp/codegen/snowpark/__snapshots__/test_python_processor.ambr @@ -545,8 +545,8 @@ # name: test_process_with_collected_functions.1 ''' ===== Contents of: output/deploy/__generated/__generated.sql ===== - EXECUTE IMMEDIATE FROM '/__generated/stagepath/main.sql'; EXECUTE IMMEDIATE FROM '/__generated/stagepath/data.sql'; + EXECUTE IMMEDIATE FROM '/__generated/stagepath/main.sql'; ''' # --- @@ -560,7 +560,7 @@ RETURNS int LANGUAGE PYTHON RUNTIME_VERSION=3.11 - IMPORTS=('/', '/stagepath/data.py', '/stagepath/extra_import1.zip', '/stagepath/some_dir_str', '/stagepath/withslash.py', '@dummy_stage_str') + IMPORTS=('/path/to/import1.py', '/path/to/import2.zip', '/stagepath/data.py') PACKAGES=('package_one==1.0.2', 'package_two', 'snowflake-snowpark-python') EXTERNAL_ACCESS_INTEGRATIONS=('integration_one', 'integration_two') SECRETS=('key1'=secret_one, 'key2'=integration_two) @@ -583,7 +583,7 @@ RETURNS int LANGUAGE PYTHON RUNTIME_VERSION=3.11 - IMPORTS=('/path/to/import1.py', '/path/to/import2.zip', '/stagepath/main.py') + IMPORTS=('/', '/stagepath/data.py', '/stagepath/extra_import1.zip', '/stagepath/main.py', '/stagepath/some_dir_str', '/stagepath/withslash.py', '@dummy_stage_str') PACKAGES=('package_one==1.0.2', 'package_two', 'snowflake-snowpark-python') EXTERNAL_ACCESS_INTEGRATIONS=('integration_one', 'integration_two') SECRETS=('key1'=secret_one, 'key2'=integration_two) diff --git a/tests/nativeapp/codegen/snowpark/test_python_processor.py b/tests/nativeapp/codegen/snowpark/test_python_processor.py index 75a73cf4bc..cea459f059 100644 --- a/tests/nativeapp/codegen/snowpark/test_python_processor.py +++ b/tests/nativeapp/codegen/snowpark/test_python_processor.py @@ -39,9 +39,6 @@ from tests.testing_utils.files_and_dirs import pushd, temp_local_dir from tests_common import IS_WINDOWS -if IS_WINDOWS: - pytest.skip("Requires further refactor to work on Windows", allow_module_level=True) - PROJECT_ROOT = Path("/path/to/project") # -------------------------------------------------------- @@ -275,6 +272,9 @@ def test_edit_setup_script_with_exec_imm_sql_noop(os_agnostic_snapshot): ) +@pytest.mark.skipif( + IS_WINDOWS, reason="Symlinks on Windows are restricted to Developer mode or admins" +) def test_edit_setup_script_with_exec_imm_sql_symlink(os_agnostic_snapshot): manifest_contents = dedent( f"""\ diff --git a/tests/nativeapp/utils.py b/tests/nativeapp/utils.py index e9f967ad4b..02c59c8e19 100644 --- a/tests/nativeapp/utils.py +++ b/tests/nativeapp/utils.py @@ -155,9 +155,11 @@ def assert_dir_snapshot(root: Path, os_agnostic_snapshot) -> None: # Verify that each file under the directory matches expectations for path in all_paths: if path.is_file(): - snapshot_contents = f"===== Contents of: {path} =====\n" + snapshot_contents = f"===== Contents of: {path.as_posix()} =====\n" snapshot_contents += path.read_text(encoding="utf-8") - assert snapshot_contents == os_agnostic_snapshot + assert ( + snapshot_contents == os_agnostic_snapshot + ), f"\nExpected:\n{os_agnostic_snapshot}\nGot:\n{snapshot_contents}" def create_native_app_project_model( diff --git a/tests/test_logs.py b/tests/test_logs.py index 92334857f8..47329d13d2 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -15,12 +15,14 @@ from __future__ import annotations import logging +import os import shutil from contextlib import contextmanager from pathlib import Path from typing import Optional import pytest +import tomlkit from snowflake.cli.api.config import config_init from snowflake.cli.api.exceptions import InvalidLogsConfiguration from snowflake.cli.app import loggers @@ -29,9 +31,6 @@ from tests.testing_utils.files_and_dirs import assert_file_permissions_are_strict from tests_common import IS_WINDOWS -if IS_WINDOWS: - pytest.skip("Requires further refactor to work on Windows", allow_module_level=True) - @pytest.fixture def setup_config_and_logs(snowflake_home): @@ -49,29 +48,21 @@ def _setup_config_and_logs( logs_path = snowflake_home / "custom" / "logs" config_path = snowflake_home / "config.toml" - config_path.write_text( - "\n".join( - x - for x in [ - "[connections]", - "", - "[cli.logs]", - f'path = "{logs_path}"' if use_custom_logs_path else None, - ( - f"save_logs = {str(save_logs).lower()}" - if save_logs is not None - else None - ), - f'level = "{level}"' if level else None, - ] - if x is not None - ) - ) + log_config_data: dict[str, str | bool] = {} + config_data = dict(connections={}, cli=dict(logs=log_config_data)) + if use_custom_logs_path: + log_config_data["path"] = str(logs_path) + if save_logs is not None: + log_config_data["save_logs"] = save_logs + if level: + log_config_data["level"] = level + tomlkit.dump(config_data, config_path.open("w")) + config_path.chmod(0o700) # Make sure we start without any leftovers - shutil.rmtree(logs_path, ignore_errors=True) clean_logging_handlers() + shutil.rmtree(logs_path, ignore_errors=True) # Setup loggers config_init(config_path) @@ -79,6 +70,10 @@ def _setup_config_and_logs( assert len(_list_handlers()) == (2 if save_logs else 1) yield logs_path + + # After the test, logging handlers still have open file handles + # Close everything so we can delete the log file + clean_logging_handlers() shutil.rmtree(logs_path, ignore_errors=True) return _setup_config_and_logs @@ -132,7 +127,7 @@ def test_logs_section_appears_in_fresh_config_file(temp_dir): config_init(config_file) assert config_file.exists() is True assert '[cli.logs]\nsave_logs = true\npath = "' in config_file.read_text() - assert '/logs"\nlevel = "info"' in config_file.read_text() + assert f'{os.sep}logs"\nlevel = "info"' in config_file.read_text() def test_logs_saved_by_default(setup_config_and_logs): @@ -194,7 +189,7 @@ def test_log_level_is_not_overriden_by_verbose_flag(capsys, setup_config_and_log def test_stdout_log_level_remains_error(capsys, setup_config_and_logs): - with setup_config_and_logs(save_logs=True, level="debug") as logs_path: + with setup_config_and_logs(save_logs=True, level="debug"): print_log_messages() captured = capsys.readouterr() assert_log_level(captured.out + captured.err, expected_level="error") @@ -211,6 +206,9 @@ def test_incorrect_log_level_in_config(setup_config_and_logs): ) +@pytest.mark.skipif( + IS_WINDOWS, reason="Permissions for new files aren't strict in Windows" +) def test_log_files_permissions(setup_config_and_logs): with setup_config_and_logs(save_logs=True) as logs_path: print_log_messages()