Repository: Lightning-AI/pytorch-lightning
Pull request: Add tests for GCS filesystem
## What does this PR do?

<!--
Please include a summary of the change and which issue is fixed.
 Please also include relevant motivation and context.
 List any dependencies that are required for this change.

If we didn't discuss your PR in Github issues there's a high chance it will not be merged.

The following links the related issue to the PR (https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
-->
Fixes #7838 

## Before submitting
- [x] Was this discussed/approved via a GitHub issue? (not for typos and docs)
- [x] Did you read the [contributor guideline](https://github.com/PyTorchLightning/pytorch-lightning/blob/master/.github/CONTRIBUTING.md), **Pull Request** section?
- [x] Did you make sure your PR does only one thing, instead of bundling different changes together?
- [ ] Did you make sure to update the documentation with your changes? (if necessary)
- [x] Did you write any new necessary tests? (not for typos and docs)
- [x] Did you verify new and existing tests pass locally with your changes?
- [ ] Did you update the [CHANGELOG](https://github.com/PyTorchLightning/pytorch-lightning/blob/master/CHANGELOG.md)? (not for typos, docs, test updates, or internal minor changes/refactorings)

<!-- For CHANGELOG separate each item in the unreleased section by a blank line to reduce collisions -->

## PR review
Anyone in the community is free to review the PR once the tests have passed.
Before you start reviewing make sure you have read [Review guidelines](https://github.com/PyTorchLightning/pytorch-lightning/wiki/Review-guidelines). In short, see the following bullet-list:

 - [x] Is this pull request ready for review? (if not, please submit in draft mode)
 - [x] Check that all items from **Before submitting** are resolved
 - [x] Make sure the title is self-explanatory and the description concisely explains the PR
 - [x] Add labels and milestones (and optionally projects) to the PR so it can be classified

## Did you have fun?
Make sure you had fun coding 🙃


Problem statement:
Add tests for working with Remote file system (s3, gs buckets)
## 🚀 Tests

Add tests for working with Remote file system (s3, gs buckets)

This would require `gcsfs` as an additional install.
```python
Trainer(default_root_dir='gs://…')
```

This would require `s3fs` as an additional install.
```python
Trainer(default_root_dir='s3://…')
```

Hints from issue discussion:

Commit message: fix test

Patch to review:
diff --git a/dockers/tpu-tests/tpu_test_cases.jsonnet b/dockers/tpu-tests/tpu_test_cases.jsonnet
index 13f70deed43ca..f1a94eeac783e 100644
--- a/dockers/tpu-tests/tpu_test_cases.jsonnet
+++ b/dockers/tpu-tests/tpu_test_cases.jsonnet
@@ -20,9 +20,12 @@ local tputests = base.BaseTest {
 
   command: utils.scriptCommand(
     |||
+      export GCS_BUCKET_PATH=gs://lightning-test/test_dir
+      pip install tensorflow
       cd pytorch-lightning
       coverage run --source=pytorch_lightning -m pytest -v --capture=no \
           pytorch_lightning/utilities/xla_device.py \
+          tests/utilities/test_remote_filesystem.py \
           tests/accelerators/test_tpu_backend.py \
           tests/models/test_tpu.py
       test_exit_code=$?
diff --git a/requirements/extra.txt b/requirements/extra.txt
index 1dd8a5184ad8c..30f262af9b39c 100644
--- a/requirements/extra.txt
+++ b/requirements/extra.txt
@@ -8,3 +8,4 @@ torchtext>=0.5
 onnxruntime>=1.3.0
 hydra-core>=1.0
 jsonargparse[signatures]>=3.14.0
+gcsfs
diff --git a/tests/utilities/test_remote_filesystem.py b/tests/utilities/test_remote_filesystem.py
new file mode 100644
index 0000000000000..15a7dbb4c9aeb
--- /dev/null
+++ b/tests/utilities/test_remote_filesystem.py
@@ -0,0 +1,106 @@
+# Copyright The PyTorch Lightning team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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 fsspec
+import pytest
+
+from pytorch_lightning import Trainer
+from pytorch_lightning.callbacks import ModelCheckpoint
+from pytorch_lightning.loggers import TensorBoardLogger
+from tests.helpers import BoringModel
+
+GCS_BUCKET_PATH = os.getenv("GCS_BUCKET_PATH", None)
+_GCS_BUCKET_PATH_AVAILABLE = GCS_BUCKET_PATH is not None
+
+gcs_fs = fsspec.filesystem("gs") if _GCS_BUCKET_PATH_AVAILABLE else None
+
+
+def gcs_path_join(dir_path):
+    return GCS_BUCKET_PATH + str(dir_path)
+
+
+def gcs_rm_dir(dir_path):
+    gcs_fs.rm(dir_path, recursive=True)
+    return True
+
+
+@pytest.mark.skipif(not _GCS_BUCKET_PATH_AVAILABLE, reason="Test requires GCS bucket path")
+def test_gcs_model_checkpoint_contents(tmpdir):
+    dir_path = gcs_path_join(tmpdir)
+
+    model = BoringModel()
+    checkpoint_callback = ModelCheckpoint(dirpath=dir_path, save_top_k=-1, save_last=True)
+    epochs = 2
+
+    trainer = Trainer(
+        default_root_dir=dir_path,
+        callbacks=[checkpoint_callback],
+        limit_train_batches=10,
+        limit_val_batches=10,
+        max_epochs=2,
+        logger=False,
+    )
+
+    trainer.fit(model)
+
+    assert checkpoint_callback.best_model_path == os.path.join(dir_path, 'epoch=1-step=19.ckpt')
+    assert checkpoint_callback.last_model_path == os.path.join(dir_path, 'last.ckpt')
+
+    expected = [f'epoch={i}-step={j}.ckpt' for i, j in zip(range(epochs), [9, 19])]
+    expected.append('last.ckpt')
+
+    gcs_ckpt_paths = [os.path.basename(path) for path in gcs_fs.listdir(dir_path, detail=False)]
+    assert gcs_ckpt_paths == expected
+
+    assert gcs_rm_dir(dir_path)
+
+
+@pytest.mark.skipif(not _GCS_BUCKET_PATH_AVAILABLE, reason="Test requires GCS bucket path")
+def test_gcs_logging(tmpdir):
+    dir_path = gcs_path_join(tmpdir)
+
+    name = "tb_versioning"
+    log_dir = os.path.join(dir_path, name)
+    gcs_fs.mkdir(log_dir)
+    expected_version = "101"
+
+    logger = TensorBoardLogger(save_dir=dir_path, name=name, version=expected_version)
+    logger.log_hyperparams({"a": 1, "b": 2, 123: 3, 3.5: 4, 5j: 5})
+
+    assert logger.version == expected_version
+
+    gcs_paths = [os.path.basename(path) for path in gcs_fs.listdir(log_dir, detail=False)]
+    gcs_paths = list(filter(lambda x: len(x) > 0, gcs_paths))
+
+    assert gcs_paths == [expected_version]
+    assert gcs_fs.listdir(os.path.join(log_dir, expected_version), detail=False)
+
+    assert gcs_rm_dir(dir_path)
+
+
+@pytest.mark.skipif(not _GCS_BUCKET_PATH_AVAILABLE, reason="Test requires GCS bucket path")
+def test_gcs_save_hparams_to_yaml_file(tmpdir):
+    dir_path = gcs_path_join(tmpdir)
+
+    model = BoringModel()
+    logger = TensorBoardLogger(save_dir=dir_path, default_hp_metric=False)
+    trainer = Trainer(max_steps=1, default_root_dir=dir_path, logger=logger)
+    assert trainer.log_dir == trainer.logger.log_dir
+    trainer.fit(model)
+
+    hparams_file = "hparams.yaml"
+    assert gcs_fs.isfile(os.path.join(trainer.log_dir, hparams_file))
+
+    assert gcs_rm_dir(dir_path)


Code review comments:
diff_hunk: @@ -8,3 +8,4 @@ torchtext>=0.5
 onnxruntime>=1.3.0
 hydra-core>=1.0
 jsonargparse[signatures]>=3.15.0
+gcsfs
path: requirements/extra.txt
text: @user1:
Add a version?