diff --git a/requirements.txt b/requirements.txt
index 7d8ffe4..904e153 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
aiocoap[linkheader]>=0.4b3
+beautifulsoup4
iotlabcli
pygithub
pytest
diff --git a/testutils/github.py b/testutils/github.py
index e188e8e..8296fdf 100644
--- a/testutils/github.py
+++ b/testutils/github.py
@@ -3,13 +3,20 @@
import re
import subprocess
+from bs4 import BeautifulSoup
from github import Github, GithubException
+STICKY_COMMENT_COMMENT = ""
GITHUB_DOMAIN = "github.com"
API_URL = "https://api.%s" % GITHUB_DOMAIN
REPO_NAME = "RIOT-OS/Release-Specs"
GITHUBTOKEN_FILE = ".riotgithubtoken"
+OUTCOME_EMOJIS = {
+ "passed": "✔",
+ "failed": "✖",
+ "skipped": "🟡",
+}
logger = logging.getLogger(__name__)
@@ -124,42 +131,137 @@ def find_task_text(issue_body, tested_task):
return None, None
-def make_comment(pytest_report, issue, task):
- comment = "### [{:02d}. {}]({})\n\n".format(
- task["spec"]["spec"],
- task["name"],
- task["url"]
- )
+def find_previous_comment(github, issue):
+ comment_comment = STICKY_COMMENT_COMMENT.format(user=github.user.name)
+ for comment in issue.get_comments():
+ if comment_comment in comment.body:
+ return comment
+ return None
+
+
+def create_comment(github, issue):
+ body = "
Test Report
\n\n"
+ body += STICKY_COMMENT_COMMENT.format(user=github.user.name)
+ body += """
+
+"""
+ try:
+ return issue.create_comment(body)
+ except GithubException as e:
+ logger.error("Unable to comment: {}".format(e))
+ return None
+
+
+def _generate_outcome_summary(pytest_report):
run_url = None
if "GITHUB_RUN_ID" in os.environ and \
"GITHUB_REPOSITORY" in os.environ and \
"GITHUB_SERVER_URL" in os.environ:
run_url = "{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/actions/runs/" \
"{GITHUB_RUN_ID}".format(**os.environ)
- comment += "{}{}{}
\n\n" \
+ outcome = "{}{}{}
\n\n" \
.format(
''.format(run_url) if run_url else '',
pytest_report.outcome.upper(), '' if run_url else ''
)
if pytest_report.longrepr:
- comment += "###### Failures\n\n"
- comment += "```\n"
- comment += str(pytest_report.longrepr)
- comment += "\n```\n\n"
+ outcome += "###### Failures\n\n"
+ outcome += "```\n"
+ outcome += str(pytest_report.longrepr)
+ outcome += "\n```\n\n"
if pytest_report.sections:
for title, body in pytest_report.sections:
- comment += "###### {}\n\n".format(title)
- comment += "```\n"
- comment += str(body)
- comment += "\n```\n\n"
- comment += " "
+ outcome += "###### {}\n\n".format(title)
+ outcome += "```\n"
+ outcome += str(body)
+ outcome += "\n```\n\n"
+ outcome += " "
+ return outcome
+
+
+def _get_tasks(comment, tbody, task):
+ tasks = []
+ task_already_exists = False
+ for row in tbody.children:
+ if row.name != 'tr':
+ continue
+ cells = row.contents[0]
+ try:
+ emoji = cells[0].decode_contents()
+ task_cell = cells[1].contents[0]
+ outcome = cells[2]
+ except IndexError:
+ logger.error("Unexpected table format in %s:\n%s",
+ comment, row)
+ task_title = task.decode_contents().strip()
+ try:
+ task_url = task['href']
+ except KeyError:
+ logger.error("Unexpected table format in %s:\n%s",
+ comment, task_cell)
+ if task_title == task["title"]:
+ task_already_exists = True
+ emoji = task["emoji"]
+ task_url = task["task_url"]
+ outcome = task["outcome"]
+ tasks.append((task_title, task_url, emoji, outcome))
+ if not task_already_exists:
+ tasks.append((task["title"].strip(), task["url"], task["emoji"],
+ task["outcome"]))
+ tasks.sort()
+
+
+def _update_soup(soup, tbody, tasks):
+ tbody.clear()
+ for task in tasks:
+ tr = soup.new_tag("tr")
+ tbody.append(tr)
+ emoji_td = soup.new_tag("td")
+ emoji_td.string = task[2]
+ tr.append(emoji_td)
+ task_td = soup.new_tag("td")
+ task_a = soup.new_tag("a", href=task[1])
+ task_a.string = task[0]
+ task_td.append(task_a)
+ tr.append(task_td)
+ outcome_td = soup.new_tag("td")
+ outcome_td.append(tasks[3])
+ tr.append(outcome_td)
+
+
+def update_comment(pytest_report, comment, task):
+ soup = BeautifulSoup(comment.body, "html.parser")
+ task["title"] = "{:02d}. {}".format(task["spec"]["spec"], task["name"]) \
+ .strip()
+ task["outcome"] = BeautifulSoup(_generate_outcome_summary(pytest_report),
+ "html.parser").details
+ task["emoji"] = OUTCOME_EMOJIS[pytest_report.outcome.lower()]
+ tbody = soup.find('tbody')
+ if tbody is None:
+ logger.error("Unable to find table body in %s:\n%s",
+ comment, comment.body)
+ return None
+ _update_soup(soup, tbody, _get_tasks(comment, tbody, task))
try:
- return issue.create_comment(comment)
+ comment.edit(soup.decode_contents())
except GithubException as e:
- logger.error("Unable to comment: {}".format(e))
+ logger.error("Unable to update comment: {}".format(e))
return None
+def make_comment(pytest_report, github, issue, task):
+ comment = find_previous_comment(github, issue)
+ if comment is None:
+ comment = create_comment(github, issue)
+ update_comment(pytest_report, comment, task)
+
+
# pylint: disable=R0911,R0912
def update_issue(pytest_report): # noqa: C901
if pytest_report.when != 'call' or pytest_report.outcome == 'skipped':
@@ -189,7 +291,7 @@ def update_issue(pytest_report): # noqa: C901
logger.warning("Unable to find task {spec}.{task} in the "
"tracking issue".format(**tested_task))
elif not task["done"]:
- comment = make_comment(pytest_report, issue, task)
+ comment = make_comment(pytest_report, github, issue, task)
if comment:
comment_url = comment.html_url
else: