zackliqcom commited on
Commit
39dbdce
·
verified ·
1 Parent(s): 215c26f

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. run_backend_ops.py +28 -2
  2. run_bench_tests.py +69 -4
  3. utils.py +226 -29
run_backend_ops.py CHANGED
@@ -15,28 +15,54 @@ import sys
15
 
16
  import pytest
17
 
18
- from utils import BIN_PATH, CMD_PREFIX, push_bundle_if_needed, run_shell_command, write_qdc_log
19
 
20
 
21
  @pytest.fixture(scope="session", autouse=True)
22
  def install():
23
  """Push llama_cpp_bundle to the device if needed."""
24
- push_bundle_if_needed(f"{BIN_PATH}/test-backend-ops")
 
 
 
 
 
25
 
26
 
27
  @pytest.mark.parametrize("type_a", ["mxfp4", "fp16", "q4_0"])
28
  def test_backend_ops_htp0(type_a):
 
 
 
 
 
 
 
29
  cmd = f"{CMD_PREFIX} GGML_HEXAGON_HOSTBUF=0 GGML_HEXAGON_EXPERIMENTAL=1 {BIN_PATH}/test-backend-ops -b HTP0 -o MUL_MAT"
30
  if type_a == "q4_0":
31
  cmd += r' -p "^(?=.*type_a=q4_0)(?!.*type_b=f32,m=576,n=512,k=576).*$"'
32
  else:
33
  cmd += f" -p type_a={type_a}"
 
 
34
  result = run_shell_command(
35
  cmd,
36
  check=False,
37
  )
 
 
38
  write_qdc_log(f"backend_ops_{type_a}.log", result.stdout or "")
 
 
 
 
 
 
 
 
 
39
  assert result.returncode == 0, f"test-backend-ops type_a={type_a} failed (exit {result.returncode})"
 
40
 
41
 
42
  if __name__ == "__main__":
 
15
 
16
  import pytest
17
 
18
+ from utils import BIN_PATH, CMD_PREFIX, push_bundle_if_needed, run_shell_command, verify_binary_exists, write_qdc_log
19
 
20
 
21
  @pytest.fixture(scope="session", autouse=True)
22
  def install():
23
  """Push llama_cpp_bundle to the device if needed."""
24
+ binary_path = f"{BIN_PATH}/test-backend-ops"
25
+ push_bundle_if_needed(binary_path)
26
+
27
+ # Verify binary exists and is executable
28
+ if not verify_binary_exists(binary_path):
29
+ raise RuntimeError(f"Required binary not found or not executable: {binary_path}")
30
 
31
 
32
  @pytest.mark.parametrize("type_a", ["mxfp4", "fp16", "q4_0"])
33
  def test_backend_ops_htp0(type_a):
34
+ print(f"[TEST] Running backend-ops test for type_a={type_a}")
35
+
36
+ # Double-check binary exists before running (paranoid check)
37
+ binary = f"{BIN_PATH}/test-backend-ops"
38
+ if not verify_binary_exists(binary):
39
+ pytest.fail(f"Binary disappeared between setup and test execution: {binary}")
40
+
41
  cmd = f"{CMD_PREFIX} GGML_HEXAGON_HOSTBUF=0 GGML_HEXAGON_EXPERIMENTAL=1 {BIN_PATH}/test-backend-ops -b HTP0 -o MUL_MAT"
42
  if type_a == "q4_0":
43
  cmd += r' -p "^(?=.*type_a=q4_0)(?!.*type_b=f32,m=576,n=512,k=576).*$"'
44
  else:
45
  cmd += f" -p type_a={type_a}"
46
+
47
+ print(f"[TEST] Executing test-backend-ops with type_a={type_a}")
48
  result = run_shell_command(
49
  cmd,
50
  check=False,
51
  )
52
+
53
+ # Save log regardless of pass/fail
54
  write_qdc_log(f"backend_ops_{type_a}.log", result.stdout or "")
55
+
56
+ if result.returncode != 0:
57
+ print(f"[TEST FAILED] test-backend-ops type_a={type_a} failed with exit code {result.returncode}")
58
+ # Print last 50 lines of output for debugging
59
+ if result.stdout:
60
+ lines = result.stdout.split("\n")
61
+ print("[TEST FAILED] Last 50 lines of output:")
62
+ print("\n".join(lines[-50:]))
63
+
64
  assert result.returncode == 0, f"test-backend-ops type_a={type_a} failed (exit {result.returncode})"
65
+ print(f"[TEST PASSED] type_a={type_a}")
66
 
67
 
68
  if __name__ == "__main__":
run_bench_tests.py CHANGED
@@ -18,7 +18,7 @@ import sys
18
 
19
  import pytest
20
 
21
- from utils import BIN_PATH, CMD_PREFIX, push_bundle_if_needed, run_shell_command, write_qdc_log
22
 
23
  MODEL_PATH = "/tmp/model.gguf"
24
  PROMPT = "What is the capital of France?"
@@ -28,12 +28,41 @@ CLI_OPTS = "--batch-size 128 -n 128 -no-cnv --seed 42"
28
  @pytest.fixture(scope="session", autouse=True)
29
  def install():
30
  """Push llama_cpp_bundle to the device and download model if needed."""
31
- push_bundle_if_needed(f"{BIN_PATH}/llama-cli")
 
 
 
32
 
33
- # Skip model download if already present
 
 
 
 
 
 
 
34
  result = run_shell_command(f"ls {MODEL_PATH}", check=False)
35
  if result.returncode != 0:
36
- run_shell_command(f'curl -L -J --output {MODEL_PATH} "<<MODEL_URL>>"')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
 
39
  @pytest.mark.parametrize(
@@ -45,14 +74,32 @@ def install():
45
  ],
46
  )
47
  def test_llama_completion(device, extra_flags):
 
 
 
 
 
 
 
 
 
 
 
 
48
  result = run_shell_command(
49
  f'{CMD_PREFIX} {BIN_PATH}/llama-completion'
50
  f' -m {MODEL_PATH} --device {device} -ngl 99 -t 4 {CLI_OPTS} {extra_flags} -fa on'
51
  f' -p "{PROMPT}"',
52
  check=False,
53
  )
 
54
  write_qdc_log(f"llama_completion_{device}.log", result.stdout or "")
 
 
 
 
55
  assert result.returncode == 0, f"llama-completion {device} failed (exit {result.returncode})"
 
56
 
57
 
58
  _DEVICE_LOG_NAME = {"none": "cpu", "GPUOpenCL": "gpu", "HTP0": "htp"}
@@ -67,13 +114,31 @@ _DEVICE_LOG_NAME = {"none": "cpu", "GPUOpenCL": "gpu", "HTP0": "htp"}
67
  ],
68
  )
69
  def test_llama_bench(device):
 
 
 
 
 
 
 
 
 
 
 
 
70
  result = run_shell_command(
71
  f"{CMD_PREFIX} {BIN_PATH}/llama-bench"
72
  f" -m {MODEL_PATH} --device {device} -ngl 99 --batch-size 128 -t 4 -p 128 -n 32",
73
  check=False,
74
  )
 
75
  write_qdc_log(f"llama_bench_{_DEVICE_LOG_NAME[device]}.log", result.stdout or "")
 
 
 
 
76
  assert result.returncode == 0, f"llama-bench {device} failed (exit {result.returncode})"
 
77
 
78
 
79
  if __name__ == "__main__":
 
18
 
19
  import pytest
20
 
21
+ from utils import BIN_PATH, CMD_PREFIX, push_bundle_if_needed, run_shell_command, verify_binary_exists, write_qdc_log
22
 
23
  MODEL_PATH = "/tmp/model.gguf"
24
  PROMPT = "What is the capital of France?"
 
28
  @pytest.fixture(scope="session", autouse=True)
29
  def install():
30
  """Push llama_cpp_bundle to the device and download model if needed."""
31
+ # Check and verify required binaries
32
+ llama_cli = f"{BIN_PATH}/llama-cli"
33
+ llama_completion = f"{BIN_PATH}/llama-completion"
34
+ llama_bench = f"{BIN_PATH}/llama-bench"
35
 
36
+ push_bundle_if_needed(llama_cli)
37
+
38
+ for binary in [llama_cli, llama_completion, llama_bench]:
39
+ if not verify_binary_exists(binary):
40
+ raise RuntimeError(f"Required binary not found or not executable: {binary}")
41
+
42
+ # Check model file
43
+ print(f"[DEBUG] Checking if model exists: {MODEL_PATH}")
44
  result = run_shell_command(f"ls {MODEL_PATH}", check=False)
45
  if result.returncode != 0:
46
+ print(f"[DEBUG] Model not found, downloading from <<MODEL_URL>>")
47
+ model_url = "<<MODEL_URL>>"
48
+ if model_url == "<<MODEL_URL>>":
49
+ print("[ERROR] MODEL_URL placeholder not replaced!")
50
+ print("[ERROR] This should be replaced by run_qdc_jobs.py during artifact creation")
51
+ raise RuntimeError("MODEL_URL placeholder not replaced")
52
+
53
+ run_shell_command(f'curl -L -J --output {MODEL_PATH} "{model_url}"')
54
+
55
+ # Verify download succeeded
56
+ verify_result = run_shell_command(f"test -f {MODEL_PATH}", check=False)
57
+ if verify_result.returncode != 0:
58
+ raise RuntimeError(f"Model download failed: {MODEL_PATH}")
59
+
60
+ # Check model file size
61
+ size_result = run_shell_command(f"ls -lh {MODEL_PATH}", check=False)
62
+ if size_result.returncode == 0:
63
+ print(f"[DEBUG] Downloaded model: {size_result.stdout.strip()}")
64
+ else:
65
+ print(f"[DEBUG] Model already exists: {MODEL_PATH}")
66
 
67
 
68
  @pytest.mark.parametrize(
 
74
  ],
75
  )
76
  def test_llama_completion(device, extra_flags):
77
+ print(f"[TEST] Running llama-completion test for device={device}")
78
+
79
+ # Verify binary and model exist
80
+ binary = f"{BIN_PATH}/llama-completion"
81
+ if not verify_binary_exists(binary):
82
+ pytest.fail(f"Binary not found: {binary}")
83
+
84
+ model_check = run_shell_command(f"test -f {MODEL_PATH}", check=False)
85
+ if model_check.returncode != 0:
86
+ pytest.fail(f"Model file not found: {MODEL_PATH}")
87
+
88
+ print(f"[TEST] Executing llama-completion on device={device}")
89
  result = run_shell_command(
90
  f'{CMD_PREFIX} {BIN_PATH}/llama-completion'
91
  f' -m {MODEL_PATH} --device {device} -ngl 99 -t 4 {CLI_OPTS} {extra_flags} -fa on'
92
  f' -p "{PROMPT}"',
93
  check=False,
94
  )
95
+
96
  write_qdc_log(f"llama_completion_{device}.log", result.stdout or "")
97
+
98
+ if result.returncode != 0:
99
+ print(f"[TEST FAILED] llama-completion device={device} failed with exit code {result.returncode}")
100
+
101
  assert result.returncode == 0, f"llama-completion {device} failed (exit {result.returncode})"
102
+ print(f"[TEST PASSED] device={device}")
103
 
104
 
105
  _DEVICE_LOG_NAME = {"none": "cpu", "GPUOpenCL": "gpu", "HTP0": "htp"}
 
114
  ],
115
  )
116
  def test_llama_bench(device):
117
+ print(f"[TEST] Running llama-bench test for device={device}")
118
+
119
+ # Verify binary and model exist
120
+ binary = f"{BIN_PATH}/llama-bench"
121
+ if not verify_binary_exists(binary):
122
+ pytest.fail(f"Binary not found: {binary}")
123
+
124
+ model_check = run_shell_command(f"test -f {MODEL_PATH}", check=False)
125
+ if model_check.returncode != 0:
126
+ pytest.fail(f"Model file not found: {MODEL_PATH}")
127
+
128
+ print(f"[TEST] Executing llama-bench on device={device}")
129
  result = run_shell_command(
130
  f"{CMD_PREFIX} {BIN_PATH}/llama-bench"
131
  f" -m {MODEL_PATH} --device {device} -ngl 99 --batch-size 128 -t 4 -p 128 -n 32",
132
  check=False,
133
  )
134
+
135
  write_qdc_log(f"llama_bench_{_DEVICE_LOG_NAME[device]}.log", result.stdout or "")
136
+
137
+ if result.returncode != 0:
138
+ print(f"[TEST FAILED] llama-bench device={device} failed with exit code {result.returncode}")
139
+
140
  assert result.returncode == 0, f"llama-bench {device} failed (exit {result.returncode})"
141
+ print(f"[TEST PASSED] device={device}")
142
 
143
 
144
  if __name__ == "__main__":
utils.py CHANGED
@@ -44,22 +44,103 @@ options.set_capability("deviceName", os.getenv("QDC_DEVICE_NAME", "QCS9075M"))
44
  # ---------------------------------------------------------------------------
45
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def run_shell_command(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess:
48
  """Run a shell command on the Linux device.
49
 
50
  For QDC Linux devices, commands are executed through the QDC infrastructure
51
  which provides SSH access to the device. The QDC Appium driver handles the
52
  SSH tunneling transparently.
 
 
 
53
  """
54
- raw = subprocess.run(
55
- ["ssh", os.getenv("QDC_DEVICE_HOST", "localhost"), f"{cmd}; echo __RC__:$?"],
56
- text=True,
57
- stdout=subprocess.PIPE,
58
- stderr=subprocess.STDOUT,
59
- timeout=300,
60
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  stdout = raw.stdout
62
  returncode = raw.returncode
 
 
63
  if stdout:
64
  lines = stdout.rstrip("\n").split("\n")
65
  if lines and lines[-1].startswith("__RC__:"):
@@ -67,39 +148,155 @@ def run_shell_command(cmd: str, *, check: bool = True) -> subprocess.CompletedPr
67
  returncode = int(lines[-1][7:])
68
  stdout = "\n".join(lines[:-1]) + "\n"
69
  except ValueError:
70
- pass
 
71
  print(stdout)
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  result = subprocess.CompletedProcess(raw.args, returncode, stdout=stdout)
73
  if check:
74
- assert returncode == 0, f"Command failed (exit {returncode})"
75
  return result
76
 
77
 
78
  def write_qdc_log(filename: str, content: str) -> None:
79
  """Write content as a log file to QDC_LOGS_PATH on the device for QDC log collection."""
80
- run_shell_command(f"mkdir -p {QDC_LOGS_PATH}", check=False)
81
- with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
82
- f.write(content)
83
- tmp_path = f.name
 
 
 
 
 
84
  try:
85
- subprocess.run(
86
- ["scp", tmp_path, f"{os.getenv('QDC_DEVICE_HOST', 'localhost')}:{QDC_LOGS_PATH}/{filename}"],
87
- stdout=subprocess.PIPE,
88
- stderr=subprocess.STDOUT,
89
- timeout=60,
90
- )
91
- finally:
92
- os.unlink(tmp_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
 
95
  def push_bundle_if_needed(check_binary: str) -> None:
96
  """Push llama_cpp_bundle to the device if check_binary is not already present."""
 
 
97
  result = run_shell_command(f"ls {check_binary}", check=False)
98
- if result.returncode != 0:
99
- subprocess.run(
100
- ["scp", "-r", "/qdc/appium/llama_cpp_bundle/", f"{os.getenv('QDC_DEVICE_HOST', 'localhost')}:/tmp/"],
101
- text=True,
102
- stdout=subprocess.PIPE,
103
- stderr=subprocess.STDOUT,
104
- timeout=120,
105
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  # ---------------------------------------------------------------------------
45
 
46
 
47
+ def verify_binary_exists(binary_path: str) -> bool:
48
+ """Verify that a binary exists and is executable.
49
+
50
+ Args:
51
+ binary_path: Full path to the binary to check
52
+
53
+ Returns:
54
+ True if binary exists and is executable, False otherwise
55
+ """
56
+ print(f"[DEBUG] Verifying binary: {binary_path}")
57
+
58
+ # Check if file exists
59
+ result = run_shell_command(f"test -f {binary_path}", check=False)
60
+ if result.returncode != 0:
61
+ print(f"[ERROR] Binary does not exist: {binary_path}")
62
+ print(f"[ERROR] Expected location: {binary_path}")
63
+ print(f"[ERROR] Bundle should be at: {BUNDLE_PATH}")
64
+ print(f"[ERROR] Check if binaries were pushed correctly")
65
+ return False
66
+
67
+ # Check if executable
68
+ result = run_shell_command(f"test -x {binary_path}", check=False)
69
+ if result.returncode != 0:
70
+ print(f"[WARNING] Binary exists but is not executable: {binary_path}")
71
+ print(f"[DEBUG] Attempting to set executable permissions")
72
+ chmod_result = run_shell_command(f"chmod +x {binary_path}", check=False)
73
+ if chmod_result.returncode != 0:
74
+ print(f"[ERROR] Failed to set executable permissions on {binary_path}")
75
+ return False
76
+ print(f"[DEBUG] Successfully set executable permissions")
77
+
78
+ # Get file info for debugging
79
+ ls_result = run_shell_command(f"ls -lh {binary_path}", check=False)
80
+ if ls_result.returncode == 0:
81
+ print(f"[DEBUG] Binary info: {ls_result.stdout.strip()}")
82
+
83
+ print(f"[DEBUG] Binary verified: {binary_path}")
84
+ return True
85
+
86
+
87
  def run_shell_command(cmd: str, *, check: bool = True) -> subprocess.CompletedProcess:
88
  """Run a shell command on the Linux device.
89
 
90
  For QDC Linux devices, commands are executed through the QDC infrastructure
91
  which provides SSH access to the device. The QDC Appium driver handles the
92
  SSH tunneling transparently.
93
+
94
+ When running directly on-device (QDC_DEVICE_HOST=localhost), executes
95
+ commands locally via shell to avoid SSH password prompts.
96
  """
97
+ device_host = os.getenv("QDC_DEVICE_HOST", "localhost")
98
+
99
+ print(f"[DEBUG] Running command on device_host='{device_host}'")
100
+ print(f"[DEBUG] Command: {cmd[:200]}{'...' if len(cmd) > 200 else ''}")
101
+
102
+ try:
103
+ # If localhost, run directly via shell (avoids SSH password prompt for on-device testing)
104
+ if device_host == "localhost":
105
+ raw = subprocess.run(
106
+ ["/bin/sh", "-c", f"{cmd}; echo __RC__:$?"],
107
+ text=True,
108
+ stdout=subprocess.PIPE,
109
+ stderr=subprocess.STDOUT,
110
+ timeout=300,
111
+ )
112
+ else:
113
+ # Remote device: use SSH
114
+ print(f"[DEBUG] Using SSH to connect to {device_host}")
115
+ raw = subprocess.run(
116
+ ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
117
+ device_host, f"{cmd}; echo __RC__:$?"],
118
+ text=True,
119
+ stdout=subprocess.PIPE,
120
+ stderr=subprocess.STDOUT,
121
+ timeout=300,
122
+ )
123
+
124
+ # Check for SSH authentication failures
125
+ if raw.returncode != 0 and raw.stdout:
126
+ if "Permission denied" in raw.stdout:
127
+ print("[ERROR] SSH authentication failed! Password required or key not set up.")
128
+ print("[ERROR] To fix: Set up passwordless SSH or set QDC_DEVICE_HOST=localhost if on-device")
129
+ elif "Connection refused" in raw.stdout:
130
+ print(f"[ERROR] SSH connection refused to {device_host}. Is SSH server running?")
131
+ elif "Host key verification failed" in raw.stdout:
132
+ print(f"[ERROR] SSH host key verification failed for {device_host}")
133
+ print("[ERROR] To fix: ssh-keyscan {device_host} >> ~/.ssh/known_hosts")
134
+
135
+ except subprocess.TimeoutExpired as e:
136
+ print(f"[ERROR] Command timed out after 300 seconds")
137
+ print(f"[ERROR] Command was: {cmd[:200]}")
138
+ raise
139
+
140
  stdout = raw.stdout
141
  returncode = raw.returncode
142
+
143
+ # Parse exit code from __RC__: sentinel
144
  if stdout:
145
  lines = stdout.rstrip("\n").split("\n")
146
  if lines and lines[-1].startswith("__RC__:"):
 
148
  returncode = int(lines[-1][7:])
149
  stdout = "\n".join(lines[:-1]) + "\n"
150
  except ValueError:
151
+ print(f"[WARNING] Failed to parse exit code from: {lines[-1]}")
152
+
153
  print(stdout)
154
+
155
+ if returncode != 0:
156
+ print(f"[ERROR] Command failed with exit code {returncode}")
157
+
158
+ # Try to provide helpful context for common errors
159
+ if "No such file or directory" in stdout:
160
+ print("[ERROR] File or directory not found. Check if binaries were pushed to device.")
161
+ print(f"[ERROR] Expected bundle path: {BUNDLE_PATH}")
162
+ elif "Permission denied" in stdout and device_host == "localhost":
163
+ print("[ERROR] Permission denied. Check file permissions or run with appropriate privileges.")
164
+ elif "not found" in stdout.lower() or "command not found" in stdout.lower():
165
+ print("[ERROR] Command not found. Check if binary exists and is in PATH or use full path.")
166
+
167
  result = subprocess.CompletedProcess(raw.args, returncode, stdout=stdout)
168
  if check:
169
+ assert returncode == 0, f"Command failed (exit {returncode})\nCommand: {cmd[:200]}\nOutput: {stdout[:500]}"
170
  return result
171
 
172
 
173
  def write_qdc_log(filename: str, content: str) -> None:
174
  """Write content as a log file to QDC_LOGS_PATH on the device for QDC log collection."""
175
+ print(f"[DEBUG] Writing QDC log: {filename} ({len(content)} bytes)")
176
+
177
+ # Ensure log directory exists
178
+ mkdir_result = run_shell_command(f"mkdir -p {QDC_LOGS_PATH}", check=False)
179
+ if mkdir_result.returncode != 0:
180
+ print(f"[WARNING] Failed to create log directory {QDC_LOGS_PATH}: {mkdir_result.stdout}")
181
+
182
+ device_host = os.getenv("QDC_DEVICE_HOST", "localhost")
183
+
184
  try:
185
+ if device_host == "localhost":
186
+ # Running on-device: write directly to filesystem
187
+ log_path = f"{QDC_LOGS_PATH}/{filename}"
188
+ with open(log_path, "w") as f:
189
+ f.write(content)
190
+ print(f"[DEBUG] Successfully wrote log to {log_path}")
191
+ else:
192
+ # Remote device: use SCP
193
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
194
+ f.write(content)
195
+ tmp_path = f.name
196
+
197
+ print(f"[DEBUG] Using SCP to transfer log to {device_host}")
198
+ result = subprocess.run(
199
+ ["scp", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
200
+ tmp_path, f"{device_host}:{QDC_LOGS_PATH}/{filename}"],
201
+ stdout=subprocess.PIPE,
202
+ stderr=subprocess.STDOUT,
203
+ timeout=60,
204
+ text=True,
205
+ )
206
+
207
+ if result.returncode != 0:
208
+ print(f"[ERROR] SCP failed with exit code {result.returncode}")
209
+ print(f"[ERROR] Output: {result.stdout}")
210
+ else:
211
+ print(f"[DEBUG] Successfully transferred log to {device_host}:{QDC_LOGS_PATH}/{filename}")
212
+
213
+ os.unlink(tmp_path)
214
+
215
+ except Exception as e:
216
+ print(f"[ERROR] Failed to write QDC log {filename}: {e}")
217
+ raise
218
 
219
 
220
  def push_bundle_if_needed(check_binary: str) -> None:
221
  """Push llama_cpp_bundle to the device if check_binary is not already present."""
222
+ print(f"[DEBUG] Checking if binary exists: {check_binary}")
223
+
224
  result = run_shell_command(f"ls {check_binary}", check=False)
225
+
226
+ if result.returncode == 0:
227
+ print(f"[DEBUG] Binary already exists on device: {check_binary}")
228
+ return
229
+
230
+ print(f"[WARNING] Binary not found: {check_binary}")
231
+ print(f"[DEBUG] Will attempt to push bundle from /qdc/appium/llama_cpp_bundle/ to {BUNDLE_PATH}")
232
+
233
+ device_host = os.getenv("QDC_DEVICE_HOST", "localhost")
234
+ source_path = "/qdc/appium/llama_cpp_bundle/"
235
+
236
+ try:
237
+ if device_host == "localhost":
238
+ # Running on-device: copy locally (if source exists)
239
+ if not os.path.exists(source_path):
240
+ print(f"[ERROR] Source bundle not found at {source_path}")
241
+ print(f"[ERROR] You may need to manually copy binaries to {BUNDLE_PATH}")
242
+ print(f"[ERROR] Expected structure: {BUNDLE_PATH}/{{bin,lib}}/")
243
+ return
244
+
245
+ print(f"[DEBUG] Copying bundle from {source_path} to /tmp/")
246
+ result = subprocess.run(
247
+ ["cp", "-r", source_path, "/tmp/"],
248
+ text=True,
249
+ stdout=subprocess.PIPE,
250
+ stderr=subprocess.STDOUT,
251
+ timeout=120,
252
+ )
253
+
254
+ if result.returncode != 0:
255
+ print(f"[ERROR] Failed to copy bundle: {result.stdout}")
256
+ else:
257
+ print(f"[DEBUG] Successfully copied bundle to {BUNDLE_PATH}")
258
+
259
+ # Verify the copy succeeded
260
+ verify = run_shell_command(f"ls {check_binary}", check=False)
261
+ if verify.returncode != 0:
262
+ print(f"[ERROR] Bundle copied but binary still not found: {check_binary}")
263
+ else:
264
+ print(f"[DEBUG] Verified binary exists after copy: {check_binary}")
265
+ else:
266
+ # Remote device: use SCP
267
+ print(f"[DEBUG] Using SCP to transfer bundle to {device_host}:/tmp/")
268
+
269
+ if not os.path.exists(source_path):
270
+ print(f"[ERROR] Source bundle not found at {source_path}")
271
+ print(f"[ERROR] Cannot push to remote device")
272
+ return
273
+
274
+ result = subprocess.run(
275
+ ["scp", "-r", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
276
+ source_path, f"{device_host}:/tmp/"],
277
+ text=True,
278
+ stdout=subprocess.PIPE,
279
+ stderr=subprocess.STDOUT,
280
+ timeout=120,
281
+ )
282
+
283
+ if result.returncode != 0:
284
+ print(f"[ERROR] SCP failed: {result.stdout}")
285
+ if "Permission denied" in result.stdout:
286
+ print("[ERROR] SSH authentication failed. Set up passwordless SSH.")
287
+ else:
288
+ print(f"[DEBUG] Successfully transferred bundle to {device_host}:{BUNDLE_PATH}")
289
+
290
+ # Verify the transfer succeeded
291
+ verify = run_shell_command(f"ls {check_binary}", check=False)
292
+ if verify.returncode != 0:
293
+ print(f"[ERROR] Bundle transferred but binary still not found: {check_binary}")
294
+ else:
295
+ print(f"[DEBUG] Verified binary exists after transfer: {check_binary}")
296
+
297
+ except subprocess.TimeoutExpired:
298
+ print(f"[ERROR] Timeout while pushing bundle (exceeded 120 seconds)")
299
+ raise
300
+ except Exception as e:
301
+ print(f"[ERROR] Unexpected error while pushing bundle: {e}")
302
+ raise