Skip to content

Commit 900c174

Browse files
committed
refactor(codeql): extract shared _stream_subprocess helper
The three query-execution functions each inlined an identical pair of stdout/stderr reader threads that append, print, and tee to a log file (6 closures total). Collapse them into one _stream_subprocess(cmd, log_file, timeout) -> (returncode, stdout, stderr) helper. Behavior preserved, including TimeoutExpired/FileNotFoundError propagation to callers. -125 lines. Add characterization tests: 25 for the pure parsing helpers (locking current behavior) and 5 that exercise _stream_subprocess directly via sh/echo (real coverage without requiring the CodeQL CLL) — return code, stdout/stderr separation, log tee, timeout, and missing-binary paths.
1 parent 344430d commit 900c174

2 files changed

Lines changed: 264 additions & 183 deletions

File tree

pure_auto_codeql/utils/codeql.py

Lines changed: 58 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,49 @@ def _format_db_error(combined_error: str, database_path: str) -> str:
2323
)
2424

2525

26+
def _stream_subprocess(
27+
cmd: List[str],
28+
log_file: Path,
29+
timeout: int,
30+
) -> Tuple[int, str, str]:
31+
"""执行子进程,实时将 stdout/stderr tee 到控制台与日志文件,返回 (returncode, stdout, stderr)。
32+
33+
多个 CodeQL 执行函数共用此逻辑(此前每处都内联了一份相同的读取线程)。
34+
与原内联实现行为一致:subprocess.TimeoutExpired / FileNotFoundError 会向外传播,
35+
由调用方的 try/except 处理。
36+
"""
37+
import threading
38+
39+
process = subprocess.Popen(
40+
cmd,
41+
stdout=subprocess.PIPE,
42+
stderr=subprocess.PIPE,
43+
text=True,
44+
bufsize=1,
45+
)
46+
47+
stdout_lines: List[str] = []
48+
stderr_lines: List[str] = []
49+
50+
def _pump(stream, sink: List[str], out) -> None:
51+
for line in stream:
52+
sink.append(line)
53+
print(line, end='', file=out, flush=True)
54+
with open(log_file, 'a', encoding='utf-8') as f:
55+
f.write(line)
56+
57+
stdout_thread = threading.Thread(target=_pump, args=(process.stdout, stdout_lines, sys.stdout))
58+
stderr_thread = threading.Thread(target=_pump, args=(process.stderr, stderr_lines, sys.stderr))
59+
stdout_thread.start()
60+
stderr_thread.start()
61+
62+
returncode = process.wait(timeout=timeout)
63+
stdout_thread.join()
64+
stderr_thread.join()
65+
66+
return returncode, ''.join(stdout_lines), ''.join(stderr_lines)
67+
68+
2669
# 功能:
2770
def detect_language_from_query(query_content: str) -> str:
2871
content = (query_content or '').lower()
@@ -487,51 +530,17 @@ def run_simple_query(
487530
start_time = time.time()
488531

489532
# 使用`codeql query run`执行简单查询,添加实时日志输出
490-
import threading
491-
process = subprocess.Popen(
533+
returncode, stdout, stderr = _stream_subprocess(
492534
[
493535
'codeql', 'query', 'run',
494536
str(query_file),
495537
'--database', resolved_database_path,
496538
f'--output={str(bqrs_path)}',
497539
],
498-
stdout=subprocess.PIPE,
499-
stderr=subprocess.PIPE,
500-
text=True,
501-
bufsize=1,
540+
log_file,
541+
timeout=600,
502542
)
503543

504-
stdout_lines = []
505-
stderr_lines = []
506-
507-
# 实时读取并输出 stdout 和 stderr
508-
def read_stdout():
509-
for line in process.stdout:
510-
stdout_lines.append(line)
511-
print(line, end='', flush=True)
512-
with open(log_file, 'a', encoding='utf-8') as f:
513-
f.write(line)
514-
515-
def read_stderr():
516-
for line in process.stderr:
517-
stderr_lines.append(line)
518-
print(line, end='', file=sys.stderr, flush=True)
519-
with open(log_file, 'a', encoding='utf-8') as f:
520-
f.write(line)
521-
522-
stdout_thread = threading.Thread(target=read_stdout)
523-
stderr_thread = threading.Thread(target=read_stderr)
524-
stdout_thread.start()
525-
stderr_thread.start()
526-
527-
# 等待进程完成
528-
returncode = process.wait(timeout=600)
529-
stdout_thread.join()
530-
stderr_thread.join()
531-
532-
stdout = ''.join(stdout_lines)
533-
stderr = ''.join(stderr_lines)
534-
535544
# 计算并显示实际执行时间
536545
execution_time = time.time() - start_time
537546
print(f"✅ CodeQL简单查询执行完成! 用时: {execution_time:.2f}秒")
@@ -569,49 +578,16 @@ def read_stderr():
569578
}
570579

571580
# 解码BQRS文件为JSON格式,以便detect_breakpoints方法能够正确解析
572-
decode_process = subprocess.Popen(
581+
decode_returncode, decode_stdout, decode_stderr = _stream_subprocess(
573582
[
574583
'codeql', 'bqrs', 'decode',
575584
'--format=json',
576585
str(bqrs_path),
577586
],
578-
stdout=subprocess.PIPE,
579-
stderr=subprocess.PIPE,
580-
text=True,
581-
bufsize=1,
587+
log_file,
588+
timeout=300,
582589
)
583590

584-
decode_stdout_lines = []
585-
decode_stderr_lines = []
586-
587-
# 实时读取并输出解码过程的 stdout 和 stderr
588-
def read_decode_stdout():
589-
for line in decode_process.stdout:
590-
decode_stdout_lines.append(line)
591-
print(line, end='', flush=True)
592-
with open(log_file, 'a', encoding='utf-8') as f:
593-
f.write(line)
594-
595-
def read_decode_stderr():
596-
for line in decode_process.stderr:
597-
decode_stderr_lines.append(line)
598-
print(line, end='', file=sys.stderr, flush=True)
599-
with open(log_file, 'a', encoding='utf-8') as f:
600-
f.write(line)
601-
602-
decode_stdout_thread = threading.Thread(target=read_decode_stdout)
603-
decode_stderr_thread = threading.Thread(target=read_decode_stderr)
604-
decode_stdout_thread.start()
605-
decode_stderr_thread.start()
606-
607-
# 等待解码进程完成
608-
decode_returncode = decode_process.wait(timeout=300)
609-
decode_stdout_thread.join()
610-
decode_stderr_thread.join()
611-
612-
decode_stdout = ''.join(decode_stdout_lines)
613-
decode_stderr = ''.join(decode_stderr_lines)
614-
615591
if decode_returncode != 0:
616592
# 合并stderr和stdout以捕获所有错误信息
617593
error_output = []
@@ -766,8 +742,7 @@ def execute_codeql_query(
766742
f.write(f"{'='*80}\n")
767743

768744
# 使用`codeql database analyze`执行查询,并使用SARIF v2.1.0输出,添加 --verbose 参数
769-
import threading
770-
process = subprocess.Popen(
745+
returncode, stdout, stderr = _stream_subprocess(
771746
[
772747
'codeql', 'database', 'analyze',
773748
'--verbose',
@@ -777,43 +752,10 @@ def execute_codeql_query(
777752
'--format=sarifv2.1.0',
778753
f'--output={str(sarif_path)}',
779754
],
780-
stdout=subprocess.PIPE,
781-
stderr=subprocess.PIPE,
782-
text=True,
783-
bufsize=1,
755+
log_file,
756+
timeout=600,
784757
)
785758

786-
stdout_lines = []
787-
stderr_lines = []
788-
789-
# 实时读取并输出 stdout 和 stderr
790-
def read_stdout():
791-
for line in process.stdout:
792-
stdout_lines.append(line)
793-
print(line, end='', flush=True)
794-
with open(log_file, 'a', encoding='utf-8') as f:
795-
f.write(line)
796-
797-
def read_stderr():
798-
for line in process.stderr:
799-
stderr_lines.append(line)
800-
print(line, end='', file=sys.stderr, flush=True)
801-
with open(log_file, 'a', encoding='utf-8') as f:
802-
f.write(line)
803-
804-
stdout_thread = threading.Thread(target=read_stdout)
805-
stderr_thread = threading.Thread(target=read_stderr)
806-
stdout_thread.start()
807-
stderr_thread.start()
808-
809-
# 等待进程完成
810-
returncode = process.wait(timeout=600)
811-
stdout_thread.join()
812-
stderr_thread.join()
813-
814-
stdout = ''.join(stdout_lines)
815-
stderr = ''.join(stderr_lines)
816-
817759
# 计算并显示实际执行时间
818760
execution_time = time.time() - start_time
819761
print(f"✅ CodeQL查询执行完成! 用时: {execution_time:.2f}秒")
@@ -937,52 +879,18 @@ def run_query_and_decode_to_text(
937879
f.write(f"{'='*80}\n")
938880

939881
# 执行 codeql query run,添加 --verbose 参数
940-
import threading
941-
process = subprocess.Popen(
882+
returncode, stdout, stderr = _stream_subprocess(
942883
[
943884
'codeql', 'query', 'run',
944885
'--verbose',
945886
str(query_file),
946887
'--database', resolved_database_path,
947888
f'--output={str(bqrs_path)}',
948889
],
949-
stdout=subprocess.PIPE,
950-
stderr=subprocess.PIPE,
951-
text=True,
952-
bufsize=1,
890+
log_file,
891+
timeout=600,
953892
)
954893

955-
stdout_lines = []
956-
stderr_lines = []
957-
958-
# 实时读取并输出 stdout 和 stderr
959-
def read_stdout():
960-
for line in process.stdout:
961-
stdout_lines.append(line)
962-
print(line, end='', flush=True)
963-
with open(log_file, 'a', encoding='utf-8') as f:
964-
f.write(line)
965-
966-
def read_stderr():
967-
for line in process.stderr:
968-
stderr_lines.append(line)
969-
print(line, end='', file=sys.stderr, flush=True)
970-
with open(log_file, 'a', encoding='utf-8') as f:
971-
f.write(line)
972-
973-
stdout_thread = threading.Thread(target=read_stdout)
974-
stderr_thread = threading.Thread(target=read_stderr)
975-
stdout_thread.start()
976-
stderr_thread.start()
977-
978-
# 等待进程完成
979-
returncode = process.wait(timeout=600)
980-
stdout_thread.join()
981-
stderr_thread.join()
982-
983-
stdout = ''.join(stdout_lines)
984-
stderr = ''.join(stderr_lines)
985-
986894
if returncode != 0:
987895
parts: List[str] = []
988896
if stderr:
@@ -1014,50 +922,17 @@ def read_stderr():
1014922
f.write(f"[{log_timestamp}] 执行 codeql bqrs decode\n")
1015923
f.write(f"{'='*80}\n")
1016924

1017-
decode_process = subprocess.Popen(
925+
decode_returncode, decode_stdout, decode_stderr = _stream_subprocess(
1018926
[
1019927
'codeql', 'bqrs', 'decode',
1020928
'--verbose',
1021929
'--format=table',
1022930
str(bqrs_path),
1023931
],
1024-
stdout=subprocess.PIPE,
1025-
stderr=subprocess.PIPE,
1026-
text=True,
1027-
bufsize=1,
932+
log_file,
933+
timeout=300,
1028934
)
1029935

1030-
decode_stdout_lines = []
1031-
decode_stderr_lines = []
1032-
1033-
# 实时读取并输出 stdout 和 stderr
1034-
def read_decode_stdout():
1035-
for line in decode_process.stdout:
1036-
decode_stdout_lines.append(line)
1037-
print(line, end='', flush=True)
1038-
with open(log_file, 'a', encoding='utf-8') as f:
1039-
f.write(line)
1040-
1041-
def read_decode_stderr():
1042-
for line in decode_process.stderr:
1043-
decode_stderr_lines.append(line)
1044-
print(line, end='', file=sys.stderr, flush=True)
1045-
with open(log_file, 'a', encoding='utf-8') as f:
1046-
f.write(line)
1047-
1048-
decode_stdout_thread = threading.Thread(target=read_decode_stdout)
1049-
decode_stderr_thread = threading.Thread(target=read_decode_stderr)
1050-
decode_stdout_thread.start()
1051-
decode_stderr_thread.start()
1052-
1053-
# 等待进程完成
1054-
decode_returncode = decode_process.wait(timeout=300)
1055-
decode_stdout_thread.join()
1056-
decode_stderr_thread.join()
1057-
1058-
decode_stdout = ''.join(decode_stdout_lines)
1059-
decode_stderr = ''.join(decode_stderr_lines)
1060-
1061936
if decode_returncode != 0:
1062937
parts: List[str] = []
1063938
if decode_stderr:

0 commit comments

Comments
 (0)