Skip to content

feat: Add unittests #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 41 additions & 41 deletions .github/workflows/action-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ on:
workflow_dispatch:
inputs:
pattern1:
description: 'Include or exclude pattern'
description: "Include or exclude pattern"
required: true
pattern2:
description: 'Include or exclude pattern'
description: "Include or exclude pattern"
required: false
pattern3:
description: 'Include or exclude pattern'
description: "Include or exclude pattern"
required: false
pattern4:
description: 'Include or exclude pattern'
description: "Include or exclude pattern"
required: false

jobs:
Expand All @@ -23,48 +23,48 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
language: ["java"]

steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}

- run: |
javatest/build
- run: |
javatest/build

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
upload: failure-only
output: sarif-results
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
upload: failure-only
output: sarif-results

- name: filter-sarif
uses: advanced-security/filter-sarif@main
with:
#patterns: |
# -**/*.java
# +**/*Exposed*
patterns: |
${{ github.event.inputs.pattern1 }}
${{ github.event.inputs.pattern2 }}
${{ github.event.inputs.pattern3 }}
${{ github.event.inputs.pattern4 }}
input: sarif-results/java.sarif
output: sarif-results/java.sarif
- name: filter-sarif
uses: advanced-security/filter-sarif@main
with:
#patterns: |
# -**/*.java
# +**/*Exposed*
patterns: |
${{ github.event.inputs.pattern1 }}
${{ github.event.inputs.pattern2 }}
${{ github.event.inputs.pattern3 }}
${{ github.event.inputs.pattern4 }}
input: sarif-results/java.sarif
output: sarif-results/java.sarif

- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sarif-results/java.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sarif-results/java.sarif

- name: Upload loc as a Build Artifact
uses: actions/upload-artifact@v4
with:
name: sarif-results
path: sarif-results
retention-days: 1
- name: Upload loc as a Build Artifact
uses: actions/upload-artifact@v4
with:
name: sarif-results
path: sarif-results
retention-days: 1
20 changes: 20 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Testing"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
tests:
name: Tests
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: "Run Unit Tests"
run: |
set -e
python -m unittest discover -s ./tests -p 'test_*.py'
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

__pycache__/
139 changes: 71 additions & 68 deletions filter_sarif.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ def fail(msg):
sys.exit(-1)


def match_path_and_rule(path, rule, patterns):
def match_path_and_rule(path: str, rule: str, patterns: list[tuple[bool, str, str]]) -> bool:
result = True
for s, fp, rp in patterns:
if match(rp, rule) and match(fp, path):
result = s
return result


def parse_pattern(line):
sepchar = ':'
escchar = '\\'
file_pattern = ''
rule_pattern = ''
def parse_pattern(line: str) -> tuple[bool, str, str]:
sepchar = ":"
escchar = "\\"
file_pattern = ""
rule_pattern = ""
seen_separator = False
sign = True

# inclusion or exclusion pattern?
uline = line
if line:
if line[0] == '-':
if line[0] == "-":
sign = False
uline = line[1:]
elif line[0] == '+':
elif line[0] == "+":
uline = line[1:]

i = 0
Expand All @@ -41,12 +41,14 @@ def parse_pattern(line):
i = i + 1
if c == sepchar:
if seen_separator:
raise Exception('Invalid pattern: "' + line + '" Contains more than one separator!')
raise Exception(
'Invalid pattern: "' + line + '" Contains more than one separator!'
)
seen_separator = True
continue
elif c == escchar:
nextc = uline[i] if (i < len(uline)) else None
if nextc in ['+' , '-', escchar, sepchar]:
if nextc in ["+", "-", escchar, sepchar]:
i = i + 1
c = nextc
if seen_separator:
Expand All @@ -55,93 +57,94 @@ def parse_pattern(line):
file_pattern = file_pattern + c

if not rule_pattern:
rule_pattern = '**'
rule_pattern = "**"

return sign, file_pattern, rule_pattern


def filter_sarif(args):
if args.split_lines:
tmp = []
for p in args.patterns:
tmp = tmp + re.split('\r?\n', p)
args.patterns = tmp

args.patterns = [parse_pattern(p) for p in args.patterns if p]

print('Given patterns:')
for s, fp, rp in args.patterns:
print(
'files: {file_pattern} rules: {rule_pattern} ({sign})'.format(
file_pattern=fp,
rule_pattern=rp,
sign='positive' if s else 'negative'
)
)

with open(args.input, 'r', encoding='utf-8') as f:
s = json.load(f)

for run in s.get('runs', []):
if run.get('results', []):
def filter_sarif(sarif: dict, patterns: list) -> dict:
for run in sarif.get("runs", []):
if run.get("results", []):
new_results = []
for r in run['results']:
if r.get('locations', []):
for r in run["results"]:
if r.get("locations", []):
new_locations = []
for l in r['locations']:
for l in r["locations"]:
# TODO: The uri field is optional. We might have to fetch the actual uri from "artifacts" via "index"
# (see https://github.com/microsoft/sarif-tutorials/blob/main/docs/2-Basics.md#-linking-results-to-artifacts)
uri = l.get('physicalLocation', {}).get('artifactLocation', {}).get('uri', None)
uri = (
l.get("physicalLocation", {})
.get("artifactLocation", {})
.get("uri", None)
)
# TODO: The ruleId field is optional and potentially ambiguous. We might have to fetch the actual
# ruleId from the rule metadata via the ruleIndex field.
# (see https://github.com/microsoft/sarif-tutorials/blob/main/docs/2-Basics.md#rule-metadata)
ruleId = r['ruleId']
if uri is None or match_path_and_rule(uri, ruleId, args.patterns):
ruleId = r["ruleId"]
if uri is None or match_path_and_rule(uri, ruleId, patterns):
new_locations.append(l)
r['locations'] = new_locations
r["locations"] = new_locations
if new_locations:
new_results.append(r)
else:
# locations array doesn't exist or is empty, so we can't match on anything
# therefore, we include the result in the output
new_results.append(r)
run['results'] = new_results
run["results"] = new_results

with open(args.output, 'w', encoding='utf-8') as f:
json.dump(s, f, indent=2)
return sarif


def main():
parser = argparse.ArgumentParser(
prog='filter-sarif'
)
parser = argparse.ArgumentParser(prog="filter-sarif")
parser.add_argument("--input", help="Input SARIF file", required=True)
parser.add_argument("--output", help="Output SARIF file", required=True)
parser.add_argument(
'--input',
help='Input SARIF file',
required=True
)
parser.add_argument(
'--output',
help='Output SARIF file',
required=True
)
parser.add_argument(
'--split-lines',
"--split-lines",
default=False,
action='store_true',
help='Split given patterns on newlines.'
)
parser.add_argument(
'patterns',
help='Inclusion and exclusion patterns.',
nargs='+'
action="store_true",
help="Split given patterns on newlines.",
)
parser.add_argument("patterns", help="Inclusion and exclusion patterns.", nargs="+")

def print_usage(args):
print(parser.format_usage())

args = parser.parse_args()
filter_sarif(args)

if args.split_lines:
tmp = []
for p in args.patterns:
tmp = tmp + re.split("\r?\n", p)
args.patterns = tmp

args.patterns = [parse_pattern(p) for p in args.patterns if p]

print("Given patterns:")
for s, fp, rp in args.patterns:
print(
"files: {file_pattern} rules: {rule_pattern} ({sign})".format(
file_pattern=fp, rule_pattern=rp, sign="positive" if s else "negative"
)
)

with open(args.input, "r", encoding="utf-8") as f:
s = json.load(f)

pre_count = len(s.get("runs", [{}])[0].get("results", []))
print(f"Input SARIF Results Count (before filtering): {pre_count}")

sarif = filter_sarif(s, args.patterns)

post_count = len(sarif.get("runs", [{}])[0].get("results", []))
print(f"Output SARIF Results Count (after filtering): {post_count}")

if post_count == pre_count:
print("!! No results were filtered. Check your filter patterns !!")

with open(args.output, "w", encoding="utf-8") as f:
json.dump(sarif, f, indent=2)


main()
if __name__ == "__main__":
main()
47 changes: 47 additions & 0 deletions tests/data/cpp.sarif
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"runs": [
{
"results": [
{
"ruleId": "cpp/stack-overflow",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "io/kb.c",
"index": 0
}
}
}
]
},
{
"ruleId": "cpp/stack-overflow",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/app.cpp",
"index": 0
}
}
}
]
},
{
"ruleId": "cpp/stack-overflow",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "_codeql_build_dir/_deps/libcurl-src/lib/http.c",
"index": 0
}
}
}
]
}
]
}
]
}
Loading