Skip to content

Commit b4d9457

Browse files
refactory gcs tests with fake test
1 parent 7247823 commit b4d9457

9 files changed

Lines changed: 566 additions & 51 deletions

File tree

devtools/Tiltfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ components = {
3030
"ddb-local": [],
3131
"sfn-local": ["ddb-local"],
3232
"airflow": ["postgresql"],
33+
"fake-gcs-server": [],
3334
}
3435

3536
# ---------------------------------------------------------------------------
@@ -93,6 +94,7 @@ load('./tilt/localbatch.tiltfile', 'setup_localbatch')
9394
load('./tilt/ddb_local.tiltfile', 'setup_ddb_local')
9495
load('./tilt/sfn_local.tiltfile', 'setup_sfn_local')
9596
load('./tilt/airflow.tiltfile', 'setup_airflow')
97+
load('./tilt/fake_gcs_server.tiltfile', 'setup_fake_gcs_server')
9698

9799
_SETUP = {
98100
"minio": setup_minio,
@@ -104,6 +106,7 @@ _SETUP = {
104106
"ddb-local": setup_ddb_local,
105107
"sfn-local": setup_sfn_local,
106108
"airflow": setup_airflow,
109+
"fake-gcs-server": setup_fake_gcs_server,
107110
}
108111

109112
# ---------------------------------------------------------------------------

devtools/pick_services.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ SERVICE_OPTIONS=(
2121
"ddb-local"
2222
"sfn-local"
2323
"airflow"
24+
"fake-gcs-server"
2425
)
2526

2627
gum style "$LOGO" \
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load('./_result.tiltfile', 'new_result')
2+
3+
def setup_fake_gcs_server(ctx):
4+
k8s_yaml(read_file('./tilt/k8s/fake-gcs-server.yaml'))
5+
k8s_yaml(read_file('./tilt/k8s/fake-gcs-secret.yaml'))
6+
k8s_yaml(read_file('./tilt/k8s/gcs-bucket-init-job.yaml'))
7+
8+
k8s_resource(
9+
'fake-gcs-server',
10+
port_forwards=['4443:4443'],
11+
links=[link('http://localhost:4443/storage/v1/b', 'fake-gcs-server buckets')],
12+
labels=['fake-gcs-server'],
13+
)
14+
15+
k8s_resource('gcs-bucket-init', resource_deps=['fake-gcs-server'],
16+
labels=['fake-gcs-server'])
17+
18+
return new_result(
19+
config={"METAFLOW_DATASTORE_SYSROOT_GS": "gs://metaflow-test/metaflow"},
20+
shell_env={"STORAGE_EMULATOR_HOST": "http://localhost:4443"},
21+
config_resources=['gcs-bucket-init'],
22+
k8s_secrets=['fake-gcs-secret'],
23+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: fake-gcs-secret
5+
type: Opaque
6+
stringData:
7+
STORAGE_EMULATOR_HOST: http://fake-gcs-server:4443
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: fake-gcs-server
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: fake-gcs-server
10+
template:
11+
metadata:
12+
labels:
13+
app: fake-gcs-server
14+
spec:
15+
containers:
16+
- name: fake-gcs-server
17+
image: fsouza/fake-gcs-server:1.49.2
18+
args: ["-scheme", "http", "-host", "0.0.0.0", "-port", "4443"]
19+
ports:
20+
- containerPort: 4443
21+
resources:
22+
requests:
23+
cpu: 50m
24+
memory: 64Mi
25+
limits:
26+
cpu: 200m
27+
memory: 128Mi
28+
---
29+
apiVersion: v1
30+
kind: Service
31+
metadata:
32+
name: fake-gcs-server
33+
spec:
34+
type: LoadBalancer
35+
selector:
36+
app: fake-gcs-server
37+
ports:
38+
- port: 4443
39+
targetPort: 4443
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: batch/v1
2+
kind: Job
3+
metadata:
4+
name: gcs-bucket-init
5+
spec:
6+
ttlSecondsAfterFinished: 120
7+
template:
8+
spec:
9+
restartPolicy: OnFailure
10+
containers:
11+
- name: init
12+
image: curlimages/curl:8.11.1
13+
command: ["/bin/sh", "-ec"]
14+
args:
15+
- |
16+
curl -sf -X POST \
17+
http://fake-gcs-server:4443/storage/v1/b \
18+
-H "Content-Type: application/json" \
19+
-d '{"name":"metaflow-test"}'
20+
echo "Bucket 'metaflow-test' created."
21+
resources:
22+
requests:
23+
cpu: 25m
24+
memory: 32Mi
25+
limits:
26+
cpu: 100m
27+
memory: 64Mi

metaflow/plugins/gcp/gs_storage_client_factory.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ def _get_gs_storage_client_default():
1212
cache_key = _get_cache_key()
1313
if cache_key not in _client_cache:
1414
from google.cloud import storage
15-
import google.auth
1615

17-
credentials, project_id = google.auth.default(scopes=storage.Client.SCOPE)
18-
_client_cache[cache_key] = storage.Client(
19-
credentials=credentials, project=project_id
20-
)
16+
if os.environ.get("STORAGE_EMULATOR_HOST"):
17+
# Emulator mode: anonymous client, no real GCP credentials needed.
18+
# google-cloud-storage routes requests to STORAGE_EMULATOR_HOST automatically.
19+
_client_cache[cache_key] = storage.Client()
20+
else:
21+
import google.auth
22+
23+
credentials, project_id = google.auth.default(scopes=storage.Client.SCOPE)
24+
_client_cache[cache_key] = storage.Client(
25+
credentials=credentials, project=project_id
26+
)
2127
return _client_cache[cache_key]
2228

2329

test/core/conftest.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,55 @@
1+
import importlib
2+
import json
13
import os
24
import sys
35
from typing import Any
46

57
import pytest
68

7-
# Ensure test/core/ is on sys.path so run_tests and metaflow_test are importable.
9+
from metaflow_test import MetaflowTest
10+
from metaflow_test.formatter import FlowFormatter
11+
12+
# Ensure test/core/ is on sys.path so metaflow_test is importable.
813
_CORE_DIR = os.path.dirname(os.path.abspath(__file__))
914
if _CORE_DIR not in sys.path:
1015
sys.path.insert(0, _CORE_DIR)
1116

1217

18+
# ---------------------------------------------------------------------------
19+
# Test discovery — owned by pytest, no dependency on run_tests.py
20+
# ---------------------------------------------------------------------------
21+
22+
23+
def _iter_graphs():
24+
root = os.path.join(_CORE_DIR, "graphs")
25+
for graphfile in os.listdir(root):
26+
if graphfile.endswith(".json") and not graphfile[0] == ".":
27+
with open(os.path.join(root, graphfile)) as f:
28+
yield json.load(f)
29+
30+
31+
def _iter_tests():
32+
root = os.path.join(_CORE_DIR, "tests")
33+
if root not in sys.path:
34+
sys.path.insert(0, root)
35+
for testfile in os.listdir(root):
36+
if testfile.endswith(".py") and not testfile[0] == ".":
37+
mod = importlib.import_module(testfile[:-3], "metaflow_test")
38+
for name in dir(mod):
39+
obj = getattr(mod, name)
40+
if (
41+
name != "MetaflowTest"
42+
and isinstance(obj, type)
43+
and issubclass(obj, MetaflowTest)
44+
):
45+
yield obj()
46+
47+
48+
# ---------------------------------------------------------------------------
49+
# pytest hooks
50+
# ---------------------------------------------------------------------------
51+
52+
1353
def pytest_addoption(parser: Any) -> None:
1454
parser.addoption(
1555
"--core-tests",
@@ -28,9 +68,6 @@ def pytest_generate_tests(metafunc: Any) -> None:
2868
return
2969

3070
try:
31-
from run_tests import iter_graphs, iter_tests
32-
from metaflow_test.formatter import FlowFormatter
33-
3471
ok_tests_raw = metafunc.config.getoption("--core-tests", default=None)
3572
ok_graphs_raw = metafunc.config.getoption("--core-graphs", default=None)
3673
ok_tests = (
@@ -54,8 +91,8 @@ def pytest_generate_tests(metafunc: Any) -> None:
5491
disable_parallel = os.environ.get("METAFLOW_CORE_DISABLE_PARALLEL", "") == "1"
5592

5693
mark = getattr(pytest.mark, marker_name)
57-
all_tests = sorted(iter_tests(), key=lambda t: t.PRIORITY)
58-
all_graphs = list(iter_graphs())
94+
all_tests = sorted(_iter_tests(), key=lambda t: t.PRIORITY)
95+
all_graphs = list(_iter_graphs())
5996

6097
params = []
6198
for graph in all_graphs:

0 commit comments

Comments
 (0)