Skip to content

Commit 390d608

Browse files
authored
Basic unit tests for operations (#81)
1 parent 726bf5c commit 390d608

File tree

22 files changed

+1132
-67
lines changed

22 files changed

+1132
-67
lines changed

sarif/operations/blame_op.py

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,38 @@
66
import os
77
import subprocess
88
import sys
9+
from typing import Callable, Iterable, List, Union
910
import urllib.parse
1011
import urllib.request
1112

1213
from sarif.sarif_file import SarifFileSet
1314

1415

16+
def _run_git_blame(repo_path: str, file_path: str) -> List[bytes]:
17+
cmd = ["git", "blame", "--porcelain", _make_path_git_compatible(file_path)]
18+
with subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=repo_path) as proc:
19+
result = []
20+
if proc.stdout:
21+
result = [x for x in proc.stdout.readlines()]
22+
23+
# Ensure process terminates
24+
proc.communicate()
25+
if proc.returncode:
26+
cmd_str = " ".join(cmd)
27+
sys.stderr.write(
28+
f"WARNING: Command `{cmd_str} "
29+
f"failed with exit code {proc.returncode} in {repo_path}\n"
30+
)
31+
32+
return result
33+
34+
1535
def enhance_with_blame(
16-
input_files: SarifFileSet, repo_path: str, output: str, output_multiple_files: bool
36+
input_files: SarifFileSet,
37+
repo_path: str,
38+
output: str,
39+
output_multiple_files: bool,
40+
run_git_blame: Callable[[str, str], List[bytes]] = _run_git_blame,
1741
):
1842
"""
1943
Enhance SARIF files with information from `git blame`. The `git` command is run in the current
@@ -26,7 +50,7 @@ def enhance_with_blame(
2650
if not os.path.isdir(repo_path):
2751
raise ValueError(f"No git repository directory found at {repo_path}")
2852

29-
_enhance_with_blame(input_files, repo_path)
53+
_enhance_with_blame(input_files, repo_path, run_git_blame)
3054

3155
for input_file in input_files:
3256
input_file_name = input_file.get_file_name()
@@ -57,7 +81,11 @@ def enhance_with_blame(
5781
)
5882

5983

60-
def _enhance_with_blame(input_files, repo_path):
84+
def _enhance_with_blame(
85+
input_files: SarifFileSet,
86+
repo_path: str,
87+
run_git_blame: Callable[[str, str], List[bytes]],
88+
):
6189
"""
6290
Run `git blame --porcelain` for each file path listed in input_files.
6391
Then enhance the results in error_list by adding a "blame" property including "hash", "author"
@@ -73,7 +101,7 @@ def _enhance_with_blame(input_files, repo_path):
73101
"in",
74102
repo_path,
75103
)
76-
file_blame_info = _run_git_blame_on_files(files_to_blame, repo_path)
104+
file_blame_info = _run_git_blame_on_files(files_to_blame, repo_path, run_git_blame)
77105

78106
# Now join up blame output with result list
79107
blame_info_count = 0
@@ -106,44 +134,40 @@ def _make_path_git_compatible(file_path):
106134
return file_path
107135

108136

109-
def _run_git_blame_on_files(files_to_blame, repo_path):
137+
def _run_git_blame_on_files(
138+
files_to_blame: Iterable[str],
139+
repo_path: str,
140+
run_git_blame: Callable[[str, str], List[bytes]],
141+
):
110142
file_blame_info = {}
111143
for file_path in files_to_blame:
112-
cmd = ["git", "blame", "--porcelain", _make_path_git_compatible(file_path)]
113-
with subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=repo_path) as proc:
114-
blame_info = {"commits": {}, "line_to_commit": {}}
115-
file_blame_info[file_path] = blame_info
116-
commit_hash: str | None = None
117-
for line_bytes in proc.stdout.readlines():
118-
# Convert byte sequence to string and remove trailing LF
119-
line_string = line_bytes.decode("utf-8")[:-1]
120-
# Now parse output from git blame --porcelain
121-
if commit_hash:
122-
if line_string.startswith("\t"):
123-
commit_hash = None
124-
# Ignore line contents = source code
125-
elif " " in line_string:
126-
space_pos = line_string.index(" ")
127-
key = line_string[0:space_pos]
128-
value = line_string[space_pos + 1 :].strip()
129-
blame_info["commits"][commit_hash][key] = value
130-
else:
131-
# e.g. "boundary"
132-
key = line_string
133-
blame_info["commits"][commit_hash][key] = True
144+
git_blame_output = run_git_blame(repo_path, file_path)
145+
blame_info = {"commits": {}, "line_to_commit": {}}
146+
file_blame_info[file_path] = blame_info
147+
commit_hash: Union[str, None] = None
148+
149+
for line_bytes in git_blame_output:
150+
# Convert byte sequence to string and remove trailing LF
151+
line_string = line_bytes.decode("utf-8")[:-1]
152+
# Now parse output from git blame --porcelain
153+
if commit_hash:
154+
if line_string.startswith("\t"):
155+
commit_hash = None
156+
# Ignore line contents = source code
157+
elif " " in line_string:
158+
space_pos = line_string.index(" ")
159+
key = line_string[0:space_pos]
160+
value = line_string[space_pos + 1 :].strip()
161+
blame_info["commits"][commit_hash][key] = value
134162
else:
135-
commit_line_info = line_string.split(" ")
136-
commit_hash = commit_line_info[0]
137-
commit_line = commit_line_info[2]
138-
blame_info["commits"].setdefault(commit_hash, {})
139-
blame_info["line_to_commit"][commit_line] = commit_hash
140-
141-
# Ensure process terminates
142-
proc.communicate()
143-
if proc.returncode:
144-
cmd_str = " ".join(cmd)
145-
sys.stderr.write(
146-
f"WARNING: Command `{cmd_str} "
147-
f"failed with exit code {proc.returncode} in {repo_path}\n"
148-
)
163+
# e.g. "boundary"
164+
key = line_string
165+
blame_info["commits"][commit_hash][key] = True
166+
else:
167+
commit_line_info = line_string.split(" ")
168+
commit_hash = commit_line_info[0]
169+
commit_line = commit_line_info[2]
170+
blame_info["commits"].setdefault(commit_hash, {})
171+
blame_info["line_to_commit"][commit_line] = commit_hash
172+
149173
return file_blame_info

sarif/operations/emacs_op.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020

2121

2222
def generate_compile(
23-
input_files: sarif_file.SarifFileSet, output: str, output_multiple_files: bool
23+
input_files: sarif_file.SarifFileSet,
24+
output: str,
25+
output_multiple_files: bool,
26+
date_val: datetime = datetime.now(),
2427
):
2528
"""
2629
Generate txt file from the input files.
2730
"""
28-
date_val = datetime.now()
29-
3031
output_file = output
3132
if output_multiple_files:
3233
for input_file in input_files:

sarif/operations/html_op.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import base64
66
from datetime import datetime
77
import os
8+
from typing import Union
89

910
from jinja2 import Environment, FileSystemLoader, select_autoescape
1011

@@ -23,15 +24,14 @@
2324

2425
def generate_html(
2526
input_files: sarif_file.SarifFileSet,
26-
image_file: str,
27+
image_file: Union[str, None],
2728
output: str,
2829
output_multiple_files: bool,
30+
date_val: datetime = datetime.now(),
2931
):
3032
"""
3133
Generate HTML file from the input files.
3234
"""
33-
date_val = datetime.now()
34-
3535
if image_file:
3636
image_mime_type = "image/" + os.path.splitext(image_file)[-1]
3737
if image_mime_type == "image/jpg":

sarif/operations/info_op.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import datetime
66
import os
77

8+
from sarif.sarif_file import SarifFileSet
9+
810
_BYTES_PER_MIB = 1024 * 1024
911
_BYTES_PER_KIB = 1024
1012

@@ -103,7 +105,7 @@ def _generate_info_to_file(sarif_files, file_out):
103105
return file_count
104106

105107

106-
def generate_info(sarif_files, output):
108+
def generate_info(sarif_files: SarifFileSet, output: str):
107109
"""
108110
Print structure information about the provided `sarif_files`.
109111
"""

sarif/operations/trend_op.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
"""
44

55
import csv
6-
from typing import Dict, List
6+
from typing import Dict, List, Literal
77

88
from sarif import sarif_file
99
from sarif.sarif_file import SarifFileSet
1010

1111
TIMESTAMP_COLUMNS = ["Date", "Tool", *sarif_file.SARIF_SEVERITIES_WITH_NONE]
1212

1313

14-
def generate_trend_csv(input_files: SarifFileSet, output_file: str, dateformat: str):
14+
def generate_trend_csv(
15+
input_files: SarifFileSet,
16+
output_file: str,
17+
dateformat: Literal["dmy", "mdy", "ymd"],
18+
) -> None:
1519
"""
1620
Generate a timeline csv of the issues from the SARIF files. Each SARIF file must contain a
1721
timestamp of the form 20211012T110000Z in its filename.

sarif/operations/word_op.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from datetime import datetime
1212
import os
13+
from typing import Union
1314

1415
import docx
1516
from docx import oxml
@@ -23,9 +24,10 @@
2324

2425
def generate_word_docs_from_sarif_inputs(
2526
input_files: sarif_file.SarifFileSet,
26-
image_file: str,
27+
image_file: Union[str, None],
2728
output: str,
2829
output_multiple_files: bool,
30+
date_val: datetime = datetime.now(),
2931
):
3032
"""
3133
Convert SARIF input to Word file output.
@@ -50,23 +52,26 @@ def generate_word_docs_from_sarif_inputs(
5052
report,
5153
os.path.join(output, output_file_name),
5254
image_file,
55+
date_val,
5356
)
5457
output_file_name = "static_analysis_output.docx"
5558
output_file = os.path.join(output, output_file_name)
5659

5760
source_description = input_files.get_description()
5861
print("Writing Word summary of", source_description, "to", output_file_name)
5962
report = input_files.get_report()
60-
_generate_word_summary(input_files, report, output_file, image_file)
63+
_generate_word_summary(input_files, report, output_file, image_file, date_val)
6164

6265

63-
def _generate_word_summary(sarif_data, report, output_file, image_file):
66+
def _generate_word_summary(
67+
sarif_data, report, output_file, image_file: Union[str, None], date_val: datetime
68+
):
6469
# Create a new document
6570
document = docx.Document()
6671

6772
severities = report.get_severities()
6873
_add_heading_and_highlevel_info(
69-
document, sarif_data, report, severities, output_file, image_file
74+
document, sarif_data, report, severities, output_file, image_file, date_val
7075
)
7176
_dump_errors_summary_by_sev(document, report, severities)
7277
_dump_each_error_in_detail(document, report, severities)
@@ -76,7 +81,13 @@ def _generate_word_summary(sarif_data, report, output_file, image_file):
7681

7782

7883
def _add_heading_and_highlevel_info(
79-
document, sarif_data, report, severities, output_file, image_path
84+
document,
85+
sarif_data,
86+
report,
87+
severities,
88+
output_file,
89+
image_path: Union[str, None],
90+
date_val: datetime,
8091
):
8192
tool_name = ", ".join(sarif_data.get_distinct_tool_names())
8293
heading = f"Sarif Summary: {tool_name}"
@@ -87,7 +98,7 @@ def _add_heading_and_highlevel_info(
8798
last_paragraph.alignment = text.WD_PARAGRAPH_ALIGNMENT.CENTER
8899

89100
document.add_heading(heading, 0)
90-
document.add_paragraph(f"Document generated on: {datetime.now()}")
101+
document.add_paragraph(f"Document generated on: {date_val}")
91102

92103
sevs = ", ".join(severities)
93104
document.add_paragraph(

0 commit comments

Comments
 (0)