Enable ado render testing (#1144)

This commit is contained in:
bmc-msft
2021-08-12 12:38:49 -04:00
committed by GitHub
parent 92ce90adb4
commit 5a8a1c998e
6 changed files with 234 additions and 22 deletions

View File

@ -4,7 +4,7 @@
# Licensed under the MIT License. # Licensed under the MIT License.
import logging import logging
from typing import Iterator, List, Optional, Union from typing import Iterator, List, Optional, Tuple, Union
from azure.devops.connection import Connection from azure.devops.connection import Connection
from azure.devops.credentials import BasicAuthentication from azure.devops.credentials import BasicAuthentication
@ -60,12 +60,19 @@ class ADO:
filename: str, filename: str,
config: ADOTemplate, config: ADOTemplate,
report: Report, report: Report,
*,
renderer: Optional[Render] = None,
): ):
self.config = config self.config = config
if renderer:
self.renderer = renderer
else:
self.renderer = Render(container, filename, report) self.renderer = Render(container, filename, report)
self.project = self.render(self.config.project)
def connect(self) -> None:
auth_token = get_secret_string_value(self.config.auth_token) auth_token = get_secret_string_value(self.config.auth_token)
self.client = get_ado_client(self.config.base_url, auth_token) self.client = get_ado_client(self.config.base_url, auth_token)
self.project = self.render(self.config.project)
def render(self, template: str) -> str: def render(self, template: str) -> str:
return self.renderer.render(template) return self.renderer.render(template)
@ -169,9 +176,8 @@ class ADO:
if document: if document:
self.client.update_work_item(document, item.id, project=self.project) self.client.update_work_item(document, item.id, project=self.project)
def create_new(self) -> None: def render_new(self) -> Tuple[str, List[JsonPatchOperation]]:
task_type = self.render(self.config.type) task_type = self.render(self.config.type)
document = [] document = []
if "System.Tags" not in self.config.ado_fields: if "System.Tags" not in self.config.ado_fields:
document.append( document.append(
@ -187,6 +193,10 @@ class ADO:
document.append( document.append(
JsonPatchOperation(op="Add", path="/fields/%s" % field, value=value) JsonPatchOperation(op="Add", path="/fields/%s" % field, value=value)
) )
return (task_type, document)
def create_new(self) -> None:
task_type, document = self.render_new()
entry = self.client.create_work_item( entry = self.client.create_work_item(
document=document, project=self.project, type=task_type document=document, project=self.project, type=task_type

View File

@ -37,13 +37,26 @@ def fail_task(report: Report, error: Exception) -> None:
class Render: class Render:
def __init__(self, container: Container, filename: str, report: Report): def __init__(
self,
container: Container,
filename: str,
report: Report,
*,
task: Optional[Task] = None,
job: Optional[Job] = None,
target_url: Optional[str] = None,
input_url: Optional[str] = None,
report_url: Optional[str] = None,
):
self.report = report self.report = report
self.container = container self.container = container
self.filename = filename self.filename = filename
if not task:
task = Task.get(report.job_id, report.task_id) task = Task.get(report.job_id, report.task_id)
if not task: if not task:
raise ValueError(f"invalid task {report.task_id}") raise ValueError(f"invalid task {report.task_id}")
if not job:
job = Job.get(report.job_id) job = Job.get(report.job_id)
if not job: if not job:
raise ValueError(f"invalid job {report.job_id}") raise ValueError(f"invalid job {report.job_id}")
@ -52,15 +65,21 @@ class Render:
self.job_config = job.config self.job_config = job.config
self.env = SandboxedEnvironment() self.env = SandboxedEnvironment()
self.target_url: Optional[str] = None self.target_url = target_url
if not self.target_url:
setup_container = get_setup_container(task.config) setup_container = get_setup_container(task.config)
if setup_container: if setup_container:
self.target_url = auth_download_url( self.target_url = auth_download_url(
setup_container, self.report.executable.replace("setup/", "", 1) setup_container, self.report.executable.replace("setup/", "", 1)
) )
if report_url:
self.report_url = report_url
else:
self.report_url = auth_download_url(container, filename) self.report_url = auth_download_url(container, filename)
self.input_url: Optional[str] = None
self.input_url = input_url
if not self.input_url:
if self.report.input_blob: if self.report.input_blob:
self.input_url = auth_download_url( self.input_url = auth_download_url(
self.report.input_blob.container, self.report.input_blob.name self.report.input_blob.container, self.report.input_blob.name

View File

@ -0,0 +1,32 @@
{
"base_url": "https://dev.azure.com/contoso",
"auth_token": "DO NOT PUT YOUR PAT HERE",
"type": "Bug",
"project": "Contoso",
"unique_fields": [
"Microsoft.VSTS.Build.FoundIn"
],
"comment": "my comment",
"ado_fields": {
"System.AssignedTo": "example@contoso.com",
"System.Tags": "OneFuzz; OneFuzz-Pipeline; example@example.com",
"System.AreaPath": "MY\\AREA Path\\Here",
"System.IterationPath": "MY\\ITERATION",
"Microsoft.VSTS.Scheduling.StoryPoints": "1",
"Microsoft.VSTS.Build.FoundIn": "{{ report.input_sha256 }}",
"System.Title": "[OneFuzz] - {{ report.crash_site }}",
"Microsoft.VSTS.TCM.ReproSteps": "This is the call stack. You may wish to confirm: <ul> {% for item in report.call_stack %} <li> {{ item }} </li> {% endfor %} </ul>"
},
"on_duplicate": {
"comment": "DEDUPED!",
"set_state": {
"Resolved": "Active"
},
"ado_fields": {
"System.IterationPath": "MY\\ITERATION"
},
"increment": [
"Microsoft.VSTS.Scheduling.StoryPoints"
]
}
}

View File

@ -0,0 +1,42 @@
[
{
"op": "Add",
"path": "/fields/System.AssignedTo",
"value": "example@contoso.com"
},
{
"op": "Add",
"path": "/fields/System.Tags",
"value": "OneFuzz; OneFuzz-Pipeline; example@example.com;Onefuzz"
},
{
"op": "Add",
"path": "/fields/System.AreaPath",
"value": "MY\\AREA Path\\Here"
},
{
"op": "Add",
"path": "/fields/System.IterationPath",
"value": "MY\\ITERATION"
},
{
"op": "Add",
"path": "/fields/Microsoft.VSTS.Scheduling.StoryPoints",
"value": "1"
},
{
"op": "Add",
"path": "/fields/Microsoft.VSTS.Build.FoundIn",
"value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"op": "Add",
"path": "/fields/System.Title",
"value": "[OneFuzz] - callfunc"
},
{
"op": "Add",
"path": "/fields/Microsoft.VSTS.TCM.ReproSteps",
"value": "This is the call stack. You may wish to confirm: <ul> <li> test.exe.exe+0x2 </li> <li> test.exe.exe+0x9 </li> <li> test.exe.exe+0x3 </li> <li> test.exe.exe+0xd </li> <li> test.exe.exe+0x8 </li> <li> test.exe.exe+0x8 </li> <li> test.exe.exe+0x2 </li> </ul>"
}
]

View File

@ -0,0 +1,23 @@
{
"call_stack": [
"test.exe.exe+0x2",
"test.exe.exe+0x9",
"test.exe.exe+0x3",
"test.exe.exe+0xd",
"test.exe.exe+0x8",
"test.exe.exe+0x8",
"test.exe.exe+0x2"
],
"call_stack_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"crash_site": "callfunc",
"crash_type": "customtype",
"executable": "test2.exe",
"input_blob": {
"account": "storageaccount1",
"container": "storagecontainer1",
"name": "%23A_B.exe.exe%2B0x0_a_b_c_0x5b1e7_a_515c01e.tar"
},
"input_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"job_id": "581e0ba6-9757-47e5-a077-dba21e604961",
"task_id": "4a949007-b59f-4a47-8418-8a2c94b4aef1"
}

View File

@ -0,0 +1,86 @@
#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import json
import unittest
from pathlib import Path
from unittest.mock import patch
from onefuzztypes.enums import OS, TaskType
from onefuzztypes.models import ADOTemplate, JobConfig, Report, TaskConfig, TaskDetails
from onefuzztypes.primitives import Container
from __app__.onefuzzlib.jobs import Job
from __app__.onefuzzlib.notifications.ado import ADO
from __app__.onefuzzlib.notifications.common import Render
from __app__.onefuzzlib.tasks.main import Task
class TestReportParse(unittest.TestCase):
def setUp(self) -> None:
self.env_patch = patch.dict(
"os.environ", {"ONEFUZZ_INSTANCE_NAME": "contoso-test"}
)
self.env_patch.start()
def tearDown(self) -> None:
self.env_patch.stop()
def test_sample(self) -> None:
expected_path = Path(__file__).parent / "data" / "ado-rendered.json"
with open(expected_path, "r") as handle:
expected_document = json.load(handle)
report_path = Path(__file__).parent / "data" / "crash-report-with-html.json"
with open(report_path, "r") as handle:
report_raw = json.load(handle)
ado_path = Path(__file__).parent / "data" / "ado-config.json"
with open(ado_path, "r") as handle:
ado_raw = json.load(handle)
report = Report.parse_obj(report_raw)
config = ADOTemplate.parse_obj(ado_raw)
container = Container("containername")
filename = "test.json"
job = Job(
config=JobConfig(project="project", name="name", build="build", duration=1)
)
task = Task(
config=TaskConfig(
job_id=job.job_id,
tags={},
containers=[],
task=TaskDetails(type=TaskType.libfuzzer_fuzz, duration=1),
),
job_id=job.job_id,
os=OS.linux,
)
renderer = Render(
container,
filename,
report,
task=task,
job=job,
target_url="https://contoso.com/1",
input_url="https://contoso.com/2",
report_url="https://contoso.com/3",
)
ado = ADO(container, filename, config, report, renderer=renderer)
work_item_type, document = ado.render_new()
self.assertEqual(work_item_type, "Bug")
as_obj = [x.as_dict() for x in document]
self.assertEqual(as_obj, expected_document)
if __name__ == "__main__":
unittest.main()