|
3 | 3 | import subprocess |
4 | 4 | import socket |
5 | 5 | import typing |
| 6 | +import platform |
| 7 | +import glob |
| 8 | +import zipfile |
| 9 | +import tarfile |
| 10 | +import signal |
| 11 | +import urllib.request |
6 | 12 |
|
7 | 13 | import pytest |
8 | 14 | import objectbox |
@@ -48,56 +54,169 @@ def create_test_store(db_path: str = "testdata", clear_db: bool = True) -> objec |
48 | 54 |
|
49 | 55 | @dataclass |
50 | 56 | class SyncServerConfig: |
51 | | - container_id: str |
| 57 | + pid: int |
52 | 58 | port: int |
53 | 59 |
|
54 | 60 |
|
| 61 | +def _get_sync_server_job_name() -> str: |
| 62 | + """Returns the GitLab CI job name for downloading the sync server based on OS and architecture.""" |
| 63 | + system = platform.system().lower() |
| 64 | + machine = platform.machine().lower() |
| 65 | + |
| 66 | + if system == "darwin": |
| 67 | + if machine in ("arm64", "aarch64"): |
| 68 | + return "b:mac-arm64-server" |
| 69 | + else: |
| 70 | + return "b:mac-x64-server" |
| 71 | + elif system == "linux": |
| 72 | + if machine in ("arm64", "aarch64"): |
| 73 | + return "b:linux-aarch64-server" |
| 74 | + else: |
| 75 | + return "b:linux-x64-server" |
| 76 | + else: |
| 77 | + raise RuntimeError(f"Unsupported platform: {system} {machine}") |
| 78 | + |
| 79 | + |
55 | 80 | def start_sync_server() -> typing.Union[SyncServerConfig, None]: |
56 | | - """ Starts the ObjectBox Sync Server in a Docker container. """ |
| 81 | + """ Downloads and starts the ObjectBox Sync Server binary. """ |
57 | 82 | current_dir = os.path.dirname(os.path.realpath(__file__)) |
58 | | - user_id = None |
59 | | - if os.name != 'nt': |
60 | | - user_id = os.getuid() |
61 | | - else: |
62 | | - user_id = 0 |
| 83 | + |
| 84 | + # Check for required environment variables |
| 85 | + ci_server_url = os.environ.get("CI_SERVER_URL") |
| 86 | + ci_job_token = os.environ.get("CI_JOB_TOKEN") |
| 87 | + |
| 88 | + if not ci_server_url or not ci_job_token: |
| 89 | + test_logger.warning("CI_SERVER_URL or CI_JOB_TOKEN not set, cannot download sync server") |
| 90 | + return None |
| 91 | + |
63 | 92 | try: |
64 | | - command = ("docker run " |
65 | | - "--rm " |
66 | | - "-d " |
67 | | - f"--volume {current_dir}:/data " |
68 | | - f"--user {user_id} " |
69 | | - "-p 127.0.0.1:9999:9999 " |
70 | | - "objectboxio/sync-server-trial " |
71 | | - "--conf sync_server_config.json") |
72 | | - logger.info("Using command to start Sync Server Docker container:" + command) |
73 | | - stdout = subprocess.run(command.split(), check=True, capture_output=True, text=True).stdout |
74 | | - container_id = stdout.strip() |
| 93 | + # GitLab artifact download configuration |
| 94 | + project_id = "4" |
| 95 | + job_name = _get_sync_server_job_name() |
| 96 | + branch = "syncdev" |
| 97 | + |
| 98 | + # URL-encode the job name (colon -> %3A) |
| 99 | + job_name_encoded = job_name.replace(":", "%3A") |
| 100 | + |
| 101 | + artifact_url = f"{ci_server_url}/api/v4/projects/{project_id}/jobs/artifacts/{branch}/download?job={job_name_encoded}" |
| 102 | + |
| 103 | + print(f"Downloading sync-server artifact from: {artifact_url}") |
| 104 | + |
| 105 | + # Download the artifact |
| 106 | + artifact_path = os.path.join(current_dir, "artifact.zip") |
| 107 | + request = urllib.request.Request(artifact_url, headers={"JOB-TOKEN": ci_job_token}) |
| 108 | + with urllib.request.urlopen(request) as response, open(artifact_path, "wb") as out_file: |
| 109 | + out_file.write(response.read()) |
| 110 | + |
| 111 | + # Extract the outer artifact zip |
| 112 | + with zipfile.ZipFile(artifact_path, "r") as zip_ref: |
| 113 | + zip_ref.extractall(current_dir) |
| 114 | + |
| 115 | + # Find and extract the sync-server archive (could be .zip or .tar.gz) |
| 116 | + artifacts_dir = os.path.join(current_dir, "artifacts") |
| 117 | + |
| 118 | + # Look for both .zip and .tar.gz files |
| 119 | + sync_server_zips = glob.glob(os.path.join(artifacts_dir, "objectbox-sync-server-*.zip")) |
| 120 | + sync_server_tarballs = glob.glob(os.path.join(artifacts_dir, "objectbox-sync-server-*.tar.gz")) |
| 121 | + |
| 122 | + if sync_server_zips: |
| 123 | + sync_server_archive = sync_server_zips[0] |
| 124 | + print(f"Found sync-server zip archive: {sync_server_archive}") |
| 125 | + with zipfile.ZipFile(sync_server_archive, "r") as zip_ref: |
| 126 | + zip_ref.extractall(current_dir) |
| 127 | + elif sync_server_tarballs: |
| 128 | + sync_server_archive = sync_server_tarballs[0] |
| 129 | + print(f"Found sync-server tar.gz archive: {sync_server_archive}") |
| 130 | + with tarfile.open(sync_server_archive, "r:gz") as tar_ref: |
| 131 | + tar_ref.extractall(current_dir) |
| 132 | + else: |
| 133 | + raise RuntimeError("Could not find objectbox-sync-server-*.zip or *.tar.gz in artifacts") |
| 134 | + |
| 135 | + # Verify sync-server executable exists |
| 136 | + sync_server_executable = os.path.join(current_dir, "sync-server") |
| 137 | + if platform.system().lower() == "windows": |
| 138 | + sync_server_executable += ".exe" |
| 139 | + |
| 140 | + if not os.path.exists(sync_server_executable): |
| 141 | + raise RuntimeError("sync-server executable not found after extraction") |
| 142 | + |
| 143 | + # Make executable on Unix systems |
| 144 | + if os.name != "nt": |
| 145 | + os.chmod(sync_server_executable, 0o755) |
| 146 | + |
| 147 | + print("Starting sync-server in background...") |
75 | 148 |
|
| 149 | + # Run sync-server in background |
| 150 | + process = subprocess.Popen( |
| 151 | + [ |
| 152 | + sync_server_executable, |
| 153 | + "--model", os.path.join(current_dir, "objectbox-model.json"), |
| 154 | + "--unsecured-no-authentication", |
| 155 | + "--debug" |
| 156 | + ], |
| 157 | + cwd=current_dir, |
| 158 | + stdout=subprocess.DEVNULL, |
| 159 | + stderr=subprocess.DEVNULL |
| 160 | + ) |
| 161 | + |
| 162 | + print(f"Sync server started with PID: {process.pid}") |
| 163 | + |
| 164 | + # Wait for server to be ready |
| 165 | + time.sleep(2) |
| 166 | + |
| 167 | + # Check if server is still running |
| 168 | + if process.poll() is not None: |
| 169 | + raise RuntimeError("Sync server failed to start") |
| 170 | + |
| 171 | + # Wait for port to be available |
76 | 172 | start_time = time.time() |
77 | 173 | while (time.time() - start_time) < 10: |
78 | 174 | try: |
79 | | - with socket.create_connection(("127.0.0.1", 9999)): |
| 175 | + with socket.create_connection(("127.0.0.1", 9999), timeout=1): |
80 | 176 | break |
81 | | - except OSError: |
82 | | - pass |
| 177 | + except (OSError, socket.timeout): |
| 178 | + time.sleep(0.5) |
83 | 179 | else: |
84 | 180 | raise RuntimeError("Timed out waiting for Sync Server to start") |
85 | 181 |
|
86 | | - test_logger.info("Started ObjectBox Sync Server in Docker") |
87 | | - return SyncServerConfig(container_id=container_id, port=9999) |
| 182 | + print("Sync server is running") |
| 183 | + return SyncServerConfig(pid=process.pid, port=9999) |
| 184 | + |
88 | 185 | except Exception as e: |
89 | | - test_logger.warning(f"Could not start ObjectBox Sync Server in Docker: {e}") |
| 186 | + test_logger.warning(f"Could not start ObjectBox Sync Server: {e}") |
90 | 187 | return None |
91 | 188 |
|
92 | 189 |
|
93 | | -def stop_sync_server(container_id: str): |
94 | | - """ Stops the ObjectBox Sync Server Docker container. """ |
| 190 | +def stop_sync_server(pid: int): |
| 191 | + """ Stops the ObjectBox Sync Server process. """ |
95 | 192 | try: |
96 | | - command = f"docker stop {container_id}" |
97 | | - subprocess.run(command.split(), check=True) |
98 | | - test_logger.info("Stopped ObjectBox Sync Server Docker container") |
| 193 | + print(f"Stopping sync server (PID: {pid})...") |
| 194 | + |
| 195 | + # Send SIGTERM (or equivalent on Windows) |
| 196 | + if os.name == "nt": |
| 197 | + subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=False) |
| 198 | + else: |
| 199 | + os.kill(pid, signal.SIGTERM) |
| 200 | + |
| 201 | + # Wait for process to stop |
| 202 | + for i in range(10): |
| 203 | + try: |
| 204 | + os.kill(pid, 0) # Check if process exists |
| 205 | + time.sleep(1) |
| 206 | + except OSError: |
| 207 | + print(f"Sync server stopped after {i + 1}s") |
| 208 | + return |
| 209 | + |
| 210 | + # Force kill if still running |
| 211 | + print("Sync server still running after 10s, sending SIGKILL...") |
| 212 | + if os.name == "nt": |
| 213 | + subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=False) |
| 214 | + else: |
| 215 | + os.kill(pid, signal.SIGKILL) |
| 216 | + |
| 217 | + print("Stopped ObjectBox Sync Server") |
99 | 218 | except Exception as e: |
100 | | - test_logger.warning(f"Could not stop ObjectBox Sync Server Docker container: {e}") |
| 219 | + test_logger.warning(f"Could not stop ObjectBox Sync Server: {e}") |
101 | 220 |
|
102 | 221 |
|
103 | 222 | def assert_equal_prop(actual, expected, default): |
|
0 commit comments