From e2162f8baea027f0730d4e5041943b8adab914c1 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Fri, 12 Feb 2021 14:05:05 +0100 Subject: [PATCH 1/2] Add basic projects support --- .github/workflows/main.yml | 49 ++++++++ Makefile | 2 +- mythx_cli/analysis/list.py | 2 +- mythx_cli/analysis/report.py | 7 +- mythx_cli/analysis/status.py | 2 +- mythx_cli/analyze/command.py | 10 +- mythx_cli/cli.py | 20 ++++ mythx_cli/formatter/base.py | 2 +- mythx_cli/formatter/json.py | 33 +++-- mythx_cli/formatter/simple_stdout.py | 40 +++++-- mythx_cli/formatter/tabular.py | 73 +++++++---- mythx_cli/formatter/util.py | 20 ++-- mythx_cli/group/close.py | 4 +- mythx_cli/group/list.py | 2 +- mythx_cli/group/open.py | 4 +- mythx_cli/project/__init__.py | 1 + mythx_cli/project/delete.py | 1 + mythx_cli/project/details.py | 1 + mythx_cli/project/list.py | 52 ++++++++ mythx_cli/project/open.py | 1 + mythx_cli/render/command.py | 10 +- mythx_cli/render/util.py | 8 +- mythx_cli/util.py | 140 ++++++++++++++++++++-- requirements.txt | 2 +- requirements_dev.txt | 18 +-- tests/common.py | 8 +- tests/conftest.py | 3 + tests/test_analyze_solidity.py | 4 +- tests/test_analyze_truffle.py | 8 +- tests/test_list.py | 8 +- tests/test_renderer.py | 7 +- tests/test_report.py | 26 ++-- tests/test_report_filter.py | 9 +- tests/test_status.py | 8 +- tests/test_version.py | 4 +- tests/testdata/analysis-list-simple.txt | 10 +- tests/testdata/analysis-list-table.txt | 22 ++-- tests/testdata/analysis-status-simple.txt | 2 +- tests/testdata/analysis-status-table.txt | 64 +++++----- tests/testdata/group-list-response.json | 8 +- tests/testdata/group-list-simple.txt | 10 +- tests/testdata/group-list-table.txt | 10 +- tests/testdata/group-status-response.json | 2 +- tests/testdata/group-status-simple.txt | 2 +- tests/testdata/group-status-table.txt | 2 +- tests/testdata/version-table.txt | 8 +- tox.ini | 8 +- 47 files changed, 520 insertions(+), 217 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 mythx_cli/project/__init__.py create mode 100644 mythx_cli/project/delete.py create mode 100644 mythx_cli/project/details.py create mode 100644 mythx_cli/project/list.py create mode 100644 mythx_cli/project/open.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..8e14639 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,49 @@ +name: MythX CLI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, pypy3] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Python dependencies + uses: py-actions/py-dependency-install@v2 + with: + path: "requirements_dev.txt" + - name: Setup tox for GH actions + run: pip install tox-gh-actions + - name: Test with tox + run: make test + - name: Upload to Coveralls + run: coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + + deploy: + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.7" + - name: Install Python dependencies + uses: py-actions/py-dependency-install@v2 + with: + path: "requirements_dev.txt" + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: make release diff --git a/Makefile b/Makefile index e70e618..f9d75b9 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ lint: ## check style with flake8 flake8 mythx_cli tests test: ## run tests quickly with the default Python - py.test -vv + pytest --cov-report html --cov-report term --cov mythx_cli tests/ test-all: ## run tests on every Python version with tox tox diff --git a/mythx_cli/analysis/list.py b/mythx_cli/analysis/list.py index c81352e..6921af7 100644 --- a/mythx_cli/analysis/list.py +++ b/mythx_cli/analysis/list.py @@ -44,5 +44,5 @@ def analysis_list(ctx, number: int) -> None: # trim result to desired result number LOGGER.debug(f"Got {len(result.analyses)} analyses, trimming to {number}") - result = AnalysisListResponse(analyses=result[:number], total=resp.total) + result = AnalysisListResponse(analyses=result.analyses[:number], total=resp.total) write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_analysis_list(result)) diff --git a/mythx_cli/analysis/report.py b/mythx_cli/analysis/report.py index 4a7241f..6ab574a 100644 --- a/mythx_cli/analysis/report.py +++ b/mythx_cli/analysis/report.py @@ -4,6 +4,7 @@ import click from mythx_models.response import AnalysisInputResponse, DetectedIssuesResponse +from pythx import Client from mythx_cli.formatter import FORMAT_RESOLVER, util from mythx_cli.formatter.base import BaseFormatter @@ -54,9 +55,10 @@ def analysis_report( """ issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ] = [] formatter: BaseFormatter = FORMAT_RESOLVER[ctx["fmt"]] + ctx["client"]: Client for uuid in uuids: LOGGER.debug(f"{uuid}: Fetching report") resp = ctx["client"].report(uuid) @@ -74,8 +76,7 @@ def analysis_report( swc_blacklist=swc_blacklist, swc_whitelist=swc_whitelist, ) - resp.uuid = uuid - issues_list.append((resp, inp)) + issues_list.append((uuid, resp, inp)) LOGGER.debug( f"{uuid}: Printing report for {len(issues_list)} issue items with sort key \"{ctx['table_sort_key']}\"" diff --git a/mythx_cli/analysis/status.py b/mythx_cli/analysis/status.py index 8a1fc6d..168f91e 100644 --- a/mythx_cli/analysis/status.py +++ b/mythx_cli/analysis/status.py @@ -22,6 +22,6 @@ def analysis_status(ctx, uuids: List[str]) -> None: """ for uuid in uuids: LOGGER.debug(f"{uuid}: Fetching status") - resp = ctx["client"].status(uuid) + resp = ctx["client"].analysis_status(uuid) LOGGER.debug(f"{uuid}: Printing status information") write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_analysis_status(resp)) diff --git a/mythx_cli/analyze/command.py b/mythx_cli/analyze/command.py index cfa019b..5098949 100644 --- a/mythx_cli/analyze/command.py +++ b/mythx_cli/analyze/command.py @@ -206,8 +206,8 @@ def analyze( if create_group: resp: GroupCreationResponse = ctx["client"].create_group(group_name=group_name) - group_id = resp.group.identifier - group_name = resp.group.name or "" + group_id = resp.identifier + group_name = resp.name or "" if group_id: # associate all following analyses to the passed or newly created group @@ -308,7 +308,7 @@ def analyze( return issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ] = [] formatter: BaseFormatter = FORMAT_RESOLVER[ctx["fmt"]] for uuid in uuids: @@ -331,8 +331,8 @@ def analyze( swc_whitelist=swc_whitelist, ) # extend response with job UUID to keep formatter logic isolated - resp.uuid = uuid - issues_list.append((resp, inp)) + # resp.uuid = uuid + issues_list.append((uuid, resp, inp)) LOGGER.debug( f"Printing report for {len(issues_list)} issue items with sort key \"{ctx['table_sort_key']}\"" diff --git a/mythx_cli/cli.py b/mythx_cli/cli.py index ae9cde4..304e779 100644 --- a/mythx_cli/cli.py +++ b/mythx_cli/cli.py @@ -18,6 +18,7 @@ from mythx_cli.group.list import group_list from mythx_cli.group.open import group_open from mythx_cli.group.status import group_status +from mythx_cli.project.list import project_list from mythx_cli.render.command import render from mythx_cli.util import update_context from mythx_cli.version.command import version @@ -205,6 +206,25 @@ def cli( cli.add_command(version) +@cli.group() +def project() -> None: + """Create, modify, and view analysis projects. + + \f + + This subcommand holds all project-related actions, such as creating, + listing, and managing projects, as well as fetching the status of one + or more groups inside a project. + """ + pass + + +from mythx_cli.project import project_list + +LOGGER.debug("Registering project commands") +project.add_command(project_list) + + @cli.group() def group() -> None: """Create, modify, and view analysis groups. diff --git a/mythx_cli/formatter/base.py b/mythx_cli/formatter/base.py index a54f668..e653c14 100644 --- a/mythx_cli/formatter/base.py +++ b/mythx_cli/formatter/base.py @@ -34,7 +34,7 @@ def format_analysis_status(resp: AnalysisStatusResponse) -> str: @abc.abstractmethod def format_detected_issues( issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ], **kwargs, ) -> str: diff --git a/mythx_cli/formatter/json.py b/mythx_cli/formatter/json.py index 2b64b71..950497a 100644 --- a/mythx_cli/formatter/json.py +++ b/mythx_cli/formatter/json.py @@ -10,6 +10,7 @@ DetectedIssuesResponse, GroupListResponse, GroupStatusResponse, + ProjectListResponse, VersionResponse, ) @@ -29,43 +30,49 @@ class JSONFormatter(BaseFormatter): def format_group_status(resp: GroupStatusResponse) -> str: """Format a group status response as compressed JSON.""" - return resp.to_json() + return resp.json(by_alias=True) + + @staticmethod + def format_project_list(resp: ProjectListResponse): + """Format a project list response as pretty-printed JSON.""" + + return resp.json(by_alias=True) @staticmethod def format_group_list(resp: GroupListResponse) -> str: """Format a group list response as compressed JSON.""" - return resp.to_json() + return resp.json(by_alias=True) @staticmethod def format_analysis_list(resp: AnalysisListResponse) -> str: """Format an analysis list response as compressed JSON.""" - return resp.to_json() + return resp.json(by_alias=True) @staticmethod def format_analysis_status(resp: AnalysisStatusResponse) -> str: """Format an analysis status response as compressed JSON.""" - return resp.to_json() + return resp.json(by_alias=True) @staticmethod def format_detected_issues( issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ], **kwargs, ) -> str: """Format an issue report response as compressed JSON.""" - output = [resp.to_dict(as_list=True) for resp, _ in issues_list] + output = [resp.dict(by_alias=True) for _, resp, _ in issues_list] return json.dumps(output) @staticmethod def format_version(resp: VersionResponse) -> str: """Format a version response as compressed JSON.""" - return resp.to_json() + return resp.json(by_alias=True) class PrettyJSONFormatter(BaseFormatter): @@ -85,9 +92,15 @@ def _print_as_json(obj, report_mode=False) -> str: json_args = {"indent": 2, "sort_keys": True} if report_mode: return json.dumps( - [resp.to_dict(as_list=True) for resp, _ in obj], **json_args + [resp.dict(by_alias=True) for _, resp, _ in obj], **json_args ) - return json.dumps(obj.to_dict(), **json_args) + return json.dumps(obj.dict(by_alias=True), **json_args) + + @staticmethod + def format_project_list(resp: ProjectListResponse): + """Format a project list response as pretty-printed JSON.""" + + return PrettyJSONFormatter._print_as_json(resp) @staticmethod def format_group_status(resp: GroupStatusResponse) -> str: @@ -116,7 +129,7 @@ def format_analysis_status(obj: AnalysisStatusResponse) -> str: @staticmethod def format_detected_issues( issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ], **kwargs, ) -> str: diff --git a/mythx_cli/formatter/simple_stdout.py b/mythx_cli/formatter/simple_stdout.py index ac6c058..aced448 100644 --- a/mythx_cli/formatter/simple_stdout.py +++ b/mythx_cli/formatter/simple_stdout.py @@ -10,6 +10,7 @@ DetectedIssuesResponse, GroupListResponse, GroupStatusResponse, + ProjectListResponse, VersionResponse, ) @@ -33,7 +34,7 @@ def format_analysis_list(resp: AnalysisListResponse) -> str: """Format an analysis list response to a simple text representation.""" res = [] - for analysis in resp: + for analysis in resp.analyses: res.append("UUID: {}".format(analysis.uuid)) res.append("Submitted at: {}".format(analysis.submitted_at)) res.append("Status: {}".format(analysis.status)) @@ -46,10 +47,10 @@ def format_group_status(resp: GroupStatusResponse) -> str: """Format a group status response to a simple text representation.""" res = [ - "ID: {}".format(resp.group.identifier), - "Name: {}".format(resp.group.name or ""), - "Created on: {}".format(resp.group.created_at), - "Status: {}".format(resp.group.status), + "ID: {}".format(resp.identifier), + "Name: {}".format(resp.name or ""), + "Created on: {}".format(resp.created_at), + "Status: {}".format(resp.status), "", ] return "\n".join(res) @@ -60,7 +61,7 @@ def format_group_list(resp: GroupListResponse) -> str: representation.""" res = [] - for group in resp: + for group in resp.groups: res.append("ID: {}".format(group.identifier)) res.append("Name: {}".format(group.name or "")) res.append("Created on: {}".format(group.created_at)) @@ -69,6 +70,21 @@ def format_group_list(resp: GroupListResponse) -> str: return "\n".join(res) + @staticmethod + def format_project_list(resp: ProjectListResponse) -> str: + """Format an analysis group response to a simple text + representation.""" + + res = [] + for project in resp.projects: + res.append("ID: {}".format(project.id)) + res.append("Name: {}".format(project.name or "")) + res.append("Created on: {}".format(project.created)) + res.append("Modified: {}".format(project.modified)) + res.append("") + + return "\n".join(res) + @staticmethod def format_analysis_status(resp: AnalysisStatusResponse) -> str: """Format an analysis status response to a simple text @@ -85,7 +101,7 @@ def format_analysis_status(resp: AnalysisStatusResponse) -> str: @staticmethod def format_detected_issues( issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ], **kwargs, ) -> str: @@ -113,10 +129,10 @@ def format_version(resp: VersionResponse) -> str: return "\n".join( [ - "API: {}".format(resp.api_version), - "Harvey: {}".format(resp.harvey_version), - "Maru: {}".format(resp.maru_version), - "Mythril: {}".format(resp.mythril_version), - "Hashed: {}".format(resp.hashed_version), + "API: {}".format(resp.api), + "Harvey: {}".format(resp.harvey), + "Maru: {}".format(resp.maru), + "Mythril: {}".format(resp.mythril), + "Hashed: {}".format(resp.hash), ] ) diff --git a/mythx_cli/formatter/tabular.py b/mythx_cli/formatter/tabular.py index 98c93f5..4b79e46 100644 --- a/mythx_cli/formatter/tabular.py +++ b/mythx_cli/formatter/tabular.py @@ -12,13 +12,14 @@ DetectedIssuesResponse, GroupListResponse, GroupStatusResponse, + ProjectListResponse, VersionResponse, ) from tabulate import tabulate from mythx_cli.formatter.base import BaseFormatter from mythx_cli.formatter.util import generate_dashboard_link -from mythx_cli.util import index_by_filename +from mythx_cli.util import SourceMap, index_by_filename class TabularFormatter(BaseFormatter): @@ -43,6 +44,16 @@ def format_analysis_list(resp: AnalysisListResponse) -> str: ] return tabulate(data, tablefmt="fancy_grid") + @staticmethod + def format_project_list(resp: ProjectListResponse) -> str: + """Format an analysis group response to a tabular representation.""" + + data = [ + (project.id, project.name, project.group_count, project.modified) + for project in resp.projects + ] + return tabulate(data, tablefmt="fancy_grid") + @staticmethod def format_group_list(resp: GroupListResponse) -> str: """Format an analysis group response to a tabular representation.""" @@ -52,7 +63,7 @@ def format_group_list(resp: GroupListResponse) -> str: group.identifier, group.status, ",".join([basename(x) for x in group.main_source_files]), - group.created_at.strftime("%Y-%m-%d %H:%M:%S%z"), + group.created_at, ) for group in resp.groups ] @@ -64,42 +75,37 @@ def format_group_status(resp: GroupStatusResponse) -> str: data = ( ( - ("ID", resp.group.identifier), - ("Name", resp.group.name or ""), - ( - "Creation Date", - resp.group.created_at.strftime("%Y-%m-%d %H:%M:%S%z"), - ), - ("Created By", resp.group.created_by), - ("Progress", "{}/100".format(resp.group.progress)), + ("ID", resp.identifier), + ("Name", resp.name or ""), + ("Creation Date", resp.created_at), + ("Created By", resp.created_by), + ("Progress", "{}/100".format(resp.progress)), ) + tuple( - zip_longest( - ("Main Sources",), resp.group.main_source_files, fillvalue="" - ) + zip_longest(("Main Sources",), resp.main_source_files, fillvalue="") ) + ( - ("Status", resp.group.status.title()), - ("Queued Analyses", resp.group.analysis_statistics.queued or 0), - ("Running Analyses", resp.group.analysis_statistics.running or 0), - ("Failed Analyses", resp.group.analysis_statistics.failed or 0), - ("Finished Analyses", resp.group.analysis_statistics.finished or 0), - ("Total Analyses", resp.group.analysis_statistics.total or 0), + ("Status", resp.status.title()), + ("Queued Analyses", resp.analysis_statistics.queued or 0), + ("Running Analyses", resp.analysis_statistics.running or 0), + ("Failed Analyses", resp.analysis_statistics.failed or 0), + ("Finished Analyses", resp.analysis_statistics.finished or 0), + ("Total Analyses", resp.analysis_statistics.total or 0), ( "High Severity Vulnerabilities", - resp.group.vulnerability_statistics.high or 0, + resp.vulnerability_statistics.high or 0, ), ( "Medium Severity Vulnerabilities", - resp.group.vulnerability_statistics.medium or 0, + resp.vulnerability_statistics.medium or 0, ), ( "Low Severity Vulnerabilities", - resp.group.vulnerability_statistics.low or 0, + resp.vulnerability_statistics.low or 0, ), ( "Unknown Severity Vulnerabilities", - resp.group.vulnerability_statistics.none or 0, + resp.vulnerability_statistics.none or 0, ), ) ) @@ -109,13 +115,28 @@ def format_group_status(resp: GroupStatusResponse) -> str: def format_analysis_status(resp: AnalysisStatusResponse) -> str: """Format an analysis status response to a tabular representation.""" - data = ((k, v) for k, v in resp.analysis.to_dict().items()) + data = ( + ("UUID", resp.uuid), + ("API Version", resp.api_version), + ("Mythril Version", resp.mythril_version), + ("Harvey Version", resp.harvey_version), + ("Maru Version", resp.maru_version), + ("Queue Time", resp.queue_time), + ("Run Time", resp.run_time), + ("Status", resp.status), + ("Submitted At", resp.submitted_at), + ("Submitted By", resp.submitted_by), + ("Tool Name", resp.client_tool_name), + ("Group ID", resp.group_id), + ("Group Name", resp.group_name), + ("Analysis Mode", resp.analysis_mode), + ) return tabulate(data, tablefmt="fancy_grid") @staticmethod def format_detected_issues( issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ], **kwargs, ) -> str: @@ -167,5 +188,5 @@ def format_detected_issues( def format_version(resp: VersionResponse) -> str: """Format a version response to a tabular representation.""" - data = ((k.title(), v) for k, v in resp.to_dict().items()) + data = ((k.title(), v) for k, v in resp.dict(by_alias=True).items()) return tabulate(data, tablefmt="fancy_grid") diff --git a/mythx_cli/formatter/util.py b/mythx_cli/formatter/util.py index 2d3396a..6b697ec 100644 --- a/mythx_cli/formatter/util.py +++ b/mythx_cli/formatter/util.py @@ -3,15 +3,9 @@ from typing import List, Union import click -from mythx_models.response import DetectedIssuesResponse, Severity +from mythx_models.response import DetectedIssuesResponse, Issue, Severity -SEVERITY_ORDER = ( - Severity.UNKNOWN, - Severity.NONE, - Severity.LOW, - Severity.MEDIUM, - Severity.HIGH, -) +SEVERITY_ORDER = ("unknown", "none", "low", "medium", "high") def get_source_location_by_offset(source: str, offset: int) -> int: @@ -99,17 +93,17 @@ def filter_report( :param swc_whitelist: A comma-separated list of SWC IDs to include :return: The filtered issue report """ - - min_severity = Severity(min_severity.title()) if min_severity else Severity.UNKNOWN + min_severity = min_severity.lower() if min_severity else SEVERITY_ORDER[0] swc_blacklist = normalize_swc_list(swc_blacklist) swc_whitelist = normalize_swc_list(swc_whitelist) new_issues = [] for report in resp.issue_reports: for issue in report.issues: - is_severe = SEVERITY_ORDER.index(issue.severity) >= SEVERITY_ORDER.index( - min_severity - ) + issue: Issue + is_severe = SEVERITY_ORDER.index( + issue.severity.lower() + ) >= SEVERITY_ORDER.index(min_severity.lower()) not_blacklisted = issue.swc_id not in swc_blacklist is_whitelisted = issue.swc_id in swc_whitelist if swc_whitelist else True diff --git a/mythx_cli/group/close.py b/mythx_cli/group/close.py index b15f10d..5ebf7ed 100644 --- a/mythx_cli/group/close.py +++ b/mythx_cli/group/close.py @@ -25,7 +25,5 @@ def group_close(ctx, identifiers: List[str]) -> None: LOGGER.debug(f"Closing group for ID {identifier}") resp: GroupCreationResponse = ctx["client"].seal_group(group_id=identifier) write_or_print( - "Closed group with ID {} and name '{}'".format( - resp.group.identifier, resp.group.name - ) + "Closed group with ID {} and name '{}'".format(resp.identifier, resp.name) ) diff --git a/mythx_cli/group/list.py b/mythx_cli/group/list.py index 24a6465..4834323 100644 --- a/mythx_cli/group/list.py +++ b/mythx_cli/group/list.py @@ -46,5 +46,5 @@ def group_list(ctx, number: int) -> None: # trim result to desired result number LOGGER.debug(f"Got {len(result.groups)} analyses, trimming to {number}") - result = GroupListResponse(groups=result[:number], total=resp.total) + result = GroupListResponse(groups=result.groups[:number], total=resp.total) write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_group_list(result)) diff --git a/mythx_cli/group/open.py b/mythx_cli/group/open.py index 87cccd5..f049f57 100644 --- a/mythx_cli/group/open.py +++ b/mythx_cli/group/open.py @@ -23,7 +23,5 @@ def group_open(ctx, name: str) -> None: LOGGER.debug(f"Opening group with name {name}") resp: GroupCreationResponse = ctx["client"].create_group(group_name=name) write_or_print( - "Opened group with ID {} and name '{}'".format( - resp.group.identifier, resp.group.name - ) + "Opened group with ID {} and name '{}'".format(resp.identifier, resp.name) ) diff --git a/mythx_cli/project/__init__.py b/mythx_cli/project/__init__.py new file mode 100644 index 0000000..6731dd5 --- /dev/null +++ b/mythx_cli/project/__init__.py @@ -0,0 +1 @@ +from .list import project_list diff --git a/mythx_cli/project/delete.py b/mythx_cli/project/delete.py new file mode 100644 index 0000000..27e8210 --- /dev/null +++ b/mythx_cli/project/delete.py @@ -0,0 +1 @@ +# https://api.mythx.io/v1/openapi#operation/deleteProject diff --git a/mythx_cli/project/details.py b/mythx_cli/project/details.py new file mode 100644 index 0000000..e0c13e4 --- /dev/null +++ b/mythx_cli/project/details.py @@ -0,0 +1 @@ +# https://api.mythx.io/v1/openapi#operation/updateProject diff --git a/mythx_cli/project/list.py b/mythx_cli/project/list.py new file mode 100644 index 0000000..cb8b737 --- /dev/null +++ b/mythx_cli/project/list.py @@ -0,0 +1,52 @@ +# https://api.mythx.io/v1/openapi#operation/listProjects + +import logging + +import click +from mythx_models.response import ProjectListResponse +from pythx import Client + +from mythx_cli.formatter import FORMAT_RESOLVER +from mythx_cli.util import write_or_print + +LOGGER = logging.getLogger("mythx-cli") + + +@click.command("list") +@click.option( + "--number", + default=5, + type=click.IntRange(min=1, max=100), # ~ 5 requests à 20 entries + show_default=True, + help="The number of most recent projects to display", +) +@click.pass_obj +def project_list(ctx, number: int) -> None: + """Get a list of analysis projects. + + \f + + :param ctx: Click context holding group-level parameters + :param number: The number of analysis projects to display + :return: + """ + + client: Client = ctx["client"] + result = ProjectListResponse(projects=[], total=0) + offset = 0 + while True: + LOGGER.debug(f"Fetching projects with offset {offset}") + resp = client.project_list(offset=offset) + if not resp.projects: + LOGGER.debug("Received empty project list response") + break + offset += len(resp.projects) + result.projects.extend(resp.projects) + if len(result.projects) >= number: + LOGGER.debug(f"Received {len(result.projects)} projects") + break + + # trim result to desired result number + LOGGER.debug(f"Got {len(result.projects)} analyses, trimming to {number}") + result = ProjectListResponse(projects=result.projects[:number], total=resp.total) + write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_project_list(result)) diff --git a/mythx_cli/project/open.py b/mythx_cli/project/open.py new file mode 100644 index 0000000..5bc106b --- /dev/null +++ b/mythx_cli/project/open.py @@ -0,0 +1 @@ +# https://api.mythx.io/v1/openapi#operation/createProject diff --git a/mythx_cli/render/command.py b/mythx_cli/render/command.py index 516904b..48059f0 100644 --- a/mythx_cli/render/command.py +++ b/mythx_cli/render/command.py @@ -107,7 +107,7 @@ def render( template = env.get_template(template_name) issues_list: List[ - Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] ] = [] if len(target) == 24: LOGGER.debug(f"Identified group target {target}") @@ -124,25 +124,25 @@ def render( click.echo( "Fetching report for analysis {}".format(analysis.uuid), err=True ) - _, resp, inp = get_analysis_info( + uuid, _, resp, inp = get_analysis_info( client=client, uuid=analysis.uuid, min_severity=min_severity, swc_blacklist=swc_blacklist, swc_whitelist=swc_whitelist, ) - issues_list.append((resp, inp)) + issues_list.append((uuid, resp, inp)) elif len(target) == 36: LOGGER.debug(f"Identified analysis target {target}") click.echo("Fetching report for analysis {}".format(target), err=True) - _, resp, inp = get_analysis_info( + uuid, _, resp, inp = get_analysis_info( client=client, uuid=target, min_severity=min_severity, swc_blacklist=swc_blacklist, swc_whitelist=swc_whitelist, ) - issues_list.append((resp, inp)) + issues_list.append((uuid, resp, inp)) else: LOGGER.debug(f"Could not identify target with length {len(target)}") raise click.UsageError( diff --git a/mythx_cli/render/util.py b/mythx_cli/render/util.py index 95db5b7..7d58f58 100644 --- a/mythx_cli/render/util.py +++ b/mythx_cli/render/util.py @@ -18,7 +18,7 @@ def get_analysis_info( min_severity: Optional[str], swc_blacklist: Optional[List[str]], swc_whitelist: Optional[List[str]], -) -> Tuple[AnalysisStatusResponse, DetectedIssuesResponse, AnalysisInputResponse]: +) -> Tuple[str, AnalysisStatusResponse, DetectedIssuesResponse, AnalysisInputResponse]: """Fetch information related to the specified analysis job UUID. Given a UUID, this function will query the MythX API for the @@ -32,7 +32,7 @@ def get_analysis_info( LOGGER.debug(f"{uuid}: Fetching input") inp: Optional[AnalysisInputResponse] = client.request_by_uuid(uuid) LOGGER.debug(f"{uuid}: Fetching status") - status: AnalysisStatusResponse = client.status(uuid) + status: AnalysisStatusResponse = client.analysis_status(uuid) LOGGER.debug(f"{uuid}: Applying SWC filters") util.filter_report( @@ -41,7 +41,5 @@ def get_analysis_info( swc_blacklist=swc_blacklist, swc_whitelist=swc_whitelist, ) - # extend response with job UUID to keep formatter logic isolated - resp.uuid = uuid - return status, resp, inp + return uuid, status, resp, inp diff --git a/mythx_cli/util.py b/mythx_cli/util.py index ba4e8ec..fae6b90 100644 --- a/mythx_cli/util.py +++ b/mythx_cli/util.py @@ -10,8 +10,134 @@ LOGGER = logging.getLogger("mythx-cli") +class SourceMapLocation: + def __init__( + self, offset: int = 0, length: int = 0, file_id: int = -1, jump_type: str = "-" + ): + self.o = int(offset) + self.l = int(length) + self.f = int(file_id) + self.j = jump_type + + @property + def offset(self) -> int: + return self.o + + @offset.setter + def offset(self, value: int) -> None: + value = int(value) + if value <= 0: + raise ValueError("Expected positive offset but received {}".format(value)) + self.o = int(value) + + @property + def length(self) -> int: + return self.l + + @length.setter + def length(self, value: int) -> None: + value = int(value) + if value <= 0: + raise ValueError("Expected positive length but received {}".format(value)) + self.l = int(value) + + @property + def file_id(self) -> int: + return self.f + + @file_id.setter + def file_id(self, value: int) -> None: + value = int(value) + if value < -1: + raise ValueError( + "Expected positive file ID or -1 but received {}".format(value) + ) + self.f = int(value) + + @property + def jump_type(self) -> str: + return self.j + + @jump_type.setter + def jump_type(self, value: str) -> None: + if value not in ("i", "o", "-"): + raise ValueError( + "Invalid jump type {}, must be one of i, o, -".format(value) + ) + self.j = value + + def to_full_component_string(self) -> str: + return "{}:{}:{}:{}".format(self.o, self.l, self.f, self.j) + + def to_short_component_string(self) -> str: + return "{}:{}:{}".format(self.o, self.l, self.f) + + def __repr__(self) -> str: + return "".format(self.to_full_component_string()) + + def __eq__(self, other: "SourceMapLocation") -> bool: + return all( + (self.o == other.o, self.l == other.l, self.f == other.f, self.j == other.j) + ) + + +class SourceMap: + def __init__(self, source_map: str): + self.components = self._decompress(source_map) + + @staticmethod + def sourcemap_reducer( + accumulator: Tuple[int, int, int, str], component: str + ) -> List[str]: + parts = component.split(":") + full = [] + for i in range(4): + part_exists = i < len(parts) and parts[i] + part = parts[i] if part_exists else accumulator[i] + full.append(part) + return full + + @staticmethod + def _decompress(source_map: str) -> List[SourceMapLocation]: + components = source_map.split(";") + accumulator = (-1, -1, -2, "-") + result = [] + + for val in components: + curr = SourceMap.sourcemap_reducer(accumulator, val) + accumulator = curr + result.append(curr) + + return [SourceMapLocation(*c) for c in result] + + def _compress(self) -> str: + compressed = [] + accumulator = (-1, -1, -2, "") + for val in self.components: + compr = [] + for i, v in enumerate((val.offset, val.length, val.file_id, val.jump_type)): + if accumulator[i] == v: + compr.append("") + else: + compr.append(str(v)) + accumulator = (val.offset, val.length, val.file_id, val.jump_type) + compressed.append(":".join(compr).rstrip(":")) + return ";".join(compressed) + + def to_compressed_sourcemap(self) -> str: + return self._compress() + + def to_decompressed_sourcemap(self) -> str: + return ";".join(map(lambda x: x.to_short_component_string(), self.components)) + + def __eq__(self, other: "SourceMap") -> bool: + return self.components == other.components + + def index_by_filename( - issues_list: List[Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]] + issues_list: List[ + Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]] + ] ): """Index the given report/input responses by filename. @@ -28,7 +154,7 @@ def index_by_filename( """ report_context = defaultdict(list) - for resp, inp in issues_list: + for uuid, resp, inp in issues_list: # initialize context with source line objects for filename, file_data in inp.sources.items(): source = file_data.get("source") @@ -45,15 +171,15 @@ def index_by_filename( for report in resp.issue_reports: for issue in report.issues: issue_entry = { - "uuid": resp.uuid, + "uuid": uuid, "swcID": issue.swc_id, "swcTitle": issue.swc_title, "description": { - "head": issue.description_short, - "tail": issue.description_long, + "head": issue.description.head, + "tail": issue.description.tail, }, "severity": issue.severity, - "testCases": issue.extra_data.get("testCases", []), + "testCases": issue.extra.get("testCases", []), } if issue.swc_id == "" or issue.swc_title == "" or not issue.locations: @@ -66,7 +192,7 @@ def index_by_filename( # skip non-text locations when we have one attached to the issue continue - for c in loc.source_map.components: + for c in SourceMap(loc.source_map).components: source_list = loc.source_list or report.source_list if not (source_list and 0 <= c.file_id < len(source_list)): # skip issues whose srcmap file ID if out of range of the source list diff --git a/requirements.txt b/requirements.txt index 54201f4..c1a4af7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==7.1.2 py-solc-x==1.1.0 -pythx==1.6.1 +pythx==1.6.2 tabulate==0.8.7 Jinja2==2.11.3 htmlmin==0.1.12 diff --git a/requirements_dev.txt b/requirements_dev.txt index b57d084..1bf64b1 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,18 +1,18 @@ -r requirements.txt bumpversion==0.6.0 -coverage==5.3 -coveralls==2.2.0 -tox==3.20.1 +coverage==5.4 +coveralls==3.0.0 +tox==3.21.4 -sphinx==3.3.1 -sphinx_rtd_theme==0.5.0 -watchdog==0.10.4 -twine==3.2.0 +sphinx==3.4.3 +sphinx_rtd_theme==0.5.1 +watchdog==1.0.2 +twine==3.3.0 black==19.3b0 isort==5.6.4 -pytest==6.1.2 +pytest==6.2.2 pytest-runner==5.2 -pytest-cov==2.10.1 +pytest-cov==2.11.1 pytest-subprocess==1.0.0 diff --git a/tests/common.py b/tests/common.py index 82aff4e..bd9bf9e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -25,7 +25,11 @@ def get_test_case(path: str, obj=None, raw=False): if obj is None: return dict_data - return obj.from_dict(dict_data) + + if obj is DetectedIssuesResponse and type(dict_data) is list: + return obj(issue_reports=dict_data) + else: + return obj(**dict_data) AST = get_test_case("testdata/test-ast.json") @@ -53,7 +57,7 @@ def mock_context( ) as analysis_list_patch, patch( "pythx.Client.group_list" ) as group_list_patch, patch( - "pythx.Client.status" + "pythx.Client.analysis_status" ) as status_patch, patch( "pythx.Client.group_status" ) as group_status_patch, patch( diff --git a/tests/conftest.py b/tests/conftest.py index 0a63c8f..aa46b5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,8 @@ +import logging import os def pytest_generate_tests(metafunc): os.environ["MYTHX_API_KEY"] = "test" + for name in logging.root.manager.loggerDict: + logging.getLogger(name).setLevel(logging.DEBUG) diff --git a/tests/test_analyze_solidity.py b/tests/test_analyze_solidity.py index 83fcd6f..87f3194 100644 --- a/tests/test_analyze_solidity.py +++ b/tests/test_analyze_solidity.py @@ -39,7 +39,7 @@ def get_high_severity_report(): issues_resp = deepcopy(ISSUES_RESPONSE) - issues_resp.issue_reports[0].issues[0].severity = Severity.HIGH + issues_resp.issue_reports[0].issues[0].severity = "high" return issues_resp @@ -157,7 +157,7 @@ def test_default_recursive_blacklist(tmp_path): ( pytest.param( ["analyze", "--async"], - SUBMISSION_RESPONSE.analysis.uuid, + SUBMISSION_RESPONSE.uuid, True, 0, id="analyze async", diff --git a/tests/test_analyze_truffle.py b/tests/test_analyze_truffle.py index e86f64a..3b50a67 100644 --- a/tests/test_analyze_truffle.py +++ b/tests/test_analyze_truffle.py @@ -53,7 +53,7 @@ def setup_truffle_project(base_path, compiled=True, switch_dir=False): def get_high_severity_report(): issues_resp = deepcopy(ISSUES_RESPONSE) - issues_resp.issue_reports[0].issues[0].severity = Severity.HIGH + issues_resp.issue_reports[0].issues[0].severity = "high" return issues_resp @@ -145,11 +145,7 @@ def test_param_yaml_override(tmp_path): "params,value,contained,retval", ( pytest.param( - ["analyze", "--async"], - SUBMISSION_RESPONSE.analysis.uuid, - True, - 0, - id="async", + ["analyze", "--async"], SUBMISSION_RESPONSE.uuid, True, 0, id="async" ), pytest.param(["analyze"], ISSUES_TABLE, True, 0, id="issue table"), pytest.param( diff --git a/tests/test_list.py b/tests/test_list.py index 9fe7ea3..5efc564 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -40,7 +40,7 @@ def test_list_json(): with mock_context(): result = runner.invoke(cli, ["--format", "json", "analysis", "list"]) - assert json.loads(result.output) == ANALYSIS_LIST.to_dict() + assert json.loads(result.output) == ANALYSIS_LIST.dict(by_alias=True) assert result.exit_code == 0 @@ -49,7 +49,7 @@ def test_group_list_json(): with mock_context(): result = runner.invoke(cli, ["--format", "json", "group", "list"]) - assert json.loads(result.output) == GROUP_LIST.to_dict() + assert json.loads(result.output) == GROUP_LIST.dict(by_alias=True) assert result.exit_code == 0 @@ -58,7 +58,7 @@ def test_list_json_pretty(): with mock_context(): result = runner.invoke(cli, ["--format", "json-pretty", "analysis", "list"]) - assert json.loads(result.output) == ANALYSIS_LIST.to_dict() + assert json.loads(result.output) == ANALYSIS_LIST.dict(by_alias=True) assert result.exit_code == 0 @@ -67,7 +67,7 @@ def test_group_list_json_pretty(): with mock_context(): result = runner.invoke(cli, ["--format", "json-pretty", "group", "list"]) - assert json.loads(result.output) == GROUP_LIST.to_dict() + assert json.loads(result.output) == GROUP_LIST.dict(by_alias=True) assert result.exit_code == 0 diff --git a/tests/test_renderer.py b/tests/test_renderer.py index 2dbc114..8114973 100644 --- a/tests/test_renderer.py +++ b/tests/test_renderer.py @@ -28,9 +28,10 @@ def assert_content(data, ident, is_template): for source in map(lambda x: x["source"], INPUT_RESPONSE.sources.values()): for line in source.split("\n"): assert escape(line.strip()) in data - for issue in ISSUES_RESPONSE: - assert issue.swc_id in data - assert issue.swc_title in data + for report in ISSUES_RESPONSE.issue_reports: + for issue in report.issues: + assert issue.swc_id in data + assert issue.swc_title in data else: assert data.strip() != "" diff --git a/tests/test_report.py b/tests/test_report.py index 0fcf6fe..1714288 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -108,7 +108,9 @@ def test_report_json(): ], ) - assert json.loads(result.output)[0] == json.loads(ISSUES_RESPONSE.to_json()) + assert json.loads(result.output)[0] == json.loads( + ISSUES_RESPONSE.json(by_alias=True) + ) assert result.exit_code == 0 @@ -129,7 +131,8 @@ def test_report_json_blacklist(): ) assert all( - x["swcID"] != "SWC-110" for x in json.loads(result.output)[0][0]["issues"] + x["swcID"] != "SWC-110" + for x in json.loads(result.output)[0]["issue_reports"][0]["issues"] ) assert result.exit_code == 0 @@ -151,7 +154,8 @@ def test_report_json_whitelist(): ) assert all( - x["swcID"] == "SWC-110" for x in json.loads(result.output)[0][0]["issues"] + x["swcID"] == "SWC-110" + for x in json.loads(result.output)[0]["issue_reports"][0]["issues"] ) assert result.exit_code == 0 @@ -173,7 +177,8 @@ def test_report_json_filter(): ) assert all( - x["swcID"] != "SWC-110" for x in json.loads(result.output)[0][0]["issues"] + x["swcID"] != "SWC-110" + for x in json.loads(result.output)[0]["issue_reports"][0]["issues"] ) assert result.exit_code == 0 @@ -192,7 +197,9 @@ def test_report_json_pretty(): ], ) - assert json.loads(result.output)[0] == json.loads(ISSUES_RESPONSE.to_json()) + assert json.loads(result.output)[0] == json.loads( + ISSUES_RESPONSE.json(by_alias=True) + ) assert result.exit_code == 0 @@ -213,7 +220,8 @@ def test_report_json_pretty_blacklist(): ) assert all( - x["swcID"] != "SWC-110" for x in json.loads(result.output)[0][0]["issues"] + x["swcID"] != "SWC-110" + for x in json.loads(result.output)[0]["issue_reports"][0]["issues"] ) assert result.exit_code == 0 @@ -235,7 +243,8 @@ def test_report_json_pretty_whitelist(): ) assert all( - x["swcID"] == "SWC-110" for x in json.loads(result.output)[0][0]["issues"] + x["swcID"] == "SWC-110" + for x in json.loads(result.output)[0]["issue_reports"][0]["issues"] ) assert result.exit_code == 0 @@ -257,7 +266,8 @@ def test_report_json_pretty_filter(): ) assert all( - x["swcID"] != "SWC-110" for x in json.loads(result.output)[0][0]["issues"] + x["swcID"] != "SWC-110" + for x in json.loads(result.output)[0]["issue_reports"][0]["issues"] ) assert result.exit_code == 0 diff --git a/tests/test_report_filter.py b/tests/test_report_filter.py index aa588b1..4438ca3 100644 --- a/tests/test_report_filter.py +++ b/tests/test_report_filter.py @@ -88,7 +88,12 @@ def test_report_filter_blacklist(blacklist, whitelist, severity, contained): resp, swc_blacklist=blacklist, swc_whitelist=whitelist, min_severity=severity ) + swc_ids = [] + for report in resp.issue_reports: + for issue in report.issues: + swc_ids.append(issue.swc_id) + if contained: - assert "SWC-110" in resp + assert "SWC-110" in swc_ids else: - assert "SWC-110" not in resp + assert "SWC-110" not in swc_ids diff --git a/tests/test_status.py b/tests/test_status.py index 2d3aff3..8c45ecf 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -51,7 +51,7 @@ def test_status_json(): ], ) - assert json.loads(result.output) == ANALYSIS_STATUS.to_dict() + assert json.loads(result.output) == ANALYSIS_STATUS.dict(by_alias=True) assert result.exit_code == 0 @@ -62,7 +62,7 @@ def test_group_status_json(): cli, ["--format", "json", "group", "status", "5dd40ca50d861d001101e888"] ) - assert json.loads(result.output) == GROUP_STATUS.to_dict() + assert json.loads(result.output) == GROUP_STATUS.dict(by_alias=True) assert result.exit_code == 0 @@ -80,7 +80,7 @@ def test_status_json_pretty(): ], ) - assert json.loads(result.output) == ANALYSIS_STATUS.to_dict() + assert json.loads(result.output) == ANALYSIS_STATUS.dict(by_alias=True) assert result.exit_code == 0 @@ -92,7 +92,7 @@ def test_group_status_json_pretty(): ["--format", "json-pretty", "group", "status", "5dd40ca50d861d001101e888"], ) - assert json.loads(result.output) == GROUP_STATUS.to_dict() + assert json.loads(result.output) == GROUP_STATUS.dict(by_alias=True) assert result.exit_code == 0 diff --git a/tests/test_version.py b/tests/test_version.py index cfa6963..9b1787f 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -36,7 +36,7 @@ def test_version_json(): with mock_context(): result = runner.invoke(cli, ["--format", "json", "version", "--api"]) - assert json.loads(result.output) == VERSION_RESPONSE.to_dict() + assert json.loads(result.output) == VERSION_RESPONSE.dict(by_alias=True) assert result.exit_code == 0 @@ -45,7 +45,7 @@ def test_version_json_pretty(): with mock_context(): result = runner.invoke(cli, ["--format", "json-pretty", "version", "--api"]) - assert json.loads(result.output) == VERSION_RESPONSE.to_dict() + assert json.loads(result.output) == VERSION_RESPONSE.dict(by_alias=True) assert result.exit_code == 0 diff --git a/tests/testdata/analysis-list-simple.txt b/tests/testdata/analysis-list-simple.txt index 68e199f..72a7434 100644 --- a/tests/testdata/analysis-list-simple.txt +++ b/tests/testdata/analysis-list-simple.txt @@ -1,20 +1,20 @@ UUID: ed6b2347-68b7-4ef3-b85c-4340ae404867 -Submitted at: 2019-09-10 17:15:11.267000+00:00 +Submitted at: 2019-09-10T17:15:11.267Z Status: Finished UUID: e6566fc9-ebc1-4d04-ae5d-6f3b1873290a -Submitted at: 2019-09-10 17:15:10.645000+00:00 +Submitted at: 2019-09-10T17:15:10.645Z Status: Finished UUID: b87f0174-ef09-4fac-9d3c-97c3fdf01782 -Submitted at: 2019-09-10 17:15:09.836000+00:00 +Submitted at: 2019-09-10T17:15:09.836Z Status: Finished UUID: 2056caf6-25d7-4ce8-a633-d10a8746d5dd -Submitted at: 2019-09-10 17:12:42.341000+00:00 +Submitted at: 2019-09-10T17:12:42.341Z Status: Finished UUID: 63eb5611-ba4b-46e8-9e40-f735a0b86fd9 -Submitted at: 2019-09-10 17:12:41.645000+00:00 +Submitted at: 2019-09-10T17:12:41.645Z Status: Finished diff --git a/tests/testdata/analysis-list-table.txt b/tests/testdata/analysis-list-table.txt index bb0a64b..0f051cf 100644 --- a/tests/testdata/analysis-list-table.txt +++ b/tests/testdata/analysis-list-table.txt @@ -1,11 +1,11 @@ -╒══════════════════════════════════════╤══════════╤═══════╤══════════════════════════════════╕ -│ ed6b2347-68b7-4ef3-b85c-4340ae404867 │ Finished │ pythx │ 2019-09-10 17:15:11.267000+00:00 │ -├──────────────────────────────────────┼──────────┼───────┼──────────────────────────────────┤ -│ e6566fc9-ebc1-4d04-ae5d-6f3b1873290a │ Finished │ pythx │ 2019-09-10 17:15:10.645000+00:00 │ -├──────────────────────────────────────┼──────────┼───────┼──────────────────────────────────┤ -│ b87f0174-ef09-4fac-9d3c-97c3fdf01782 │ Finished │ pythx │ 2019-09-10 17:15:09.836000+00:00 │ -├──────────────────────────────────────┼──────────┼───────┼──────────────────────────────────┤ -│ 2056caf6-25d7-4ce8-a633-d10a8746d5dd │ Finished │ pythx │ 2019-09-10 17:12:42.341000+00:00 │ -├──────────────────────────────────────┼──────────┼───────┼──────────────────────────────────┤ -│ 63eb5611-ba4b-46e8-9e40-f735a0b86fd9 │ Finished │ pythx │ 2019-09-10 17:12:41.645000+00:00 │ -╘══════════════════════════════════════╧══════════╧═══════╧══════════════════════════════════╛ +╒══════════════════════════════════════╤══════════╤═══════╤══════════════════════════╕ +│ ed6b2347-68b7-4ef3-b85c-4340ae404867 │ Finished │ pythx │ 2019-09-10T17:15:11.267Z │ +├──────────────────────────────────────┼──────────┼───────┼──────────────────────────┤ +│ e6566fc9-ebc1-4d04-ae5d-6f3b1873290a │ Finished │ pythx │ 2019-09-10T17:15:10.645Z │ +├──────────────────────────────────────┼──────────┼───────┼──────────────────────────┤ +│ b87f0174-ef09-4fac-9d3c-97c3fdf01782 │ Finished │ pythx │ 2019-09-10T17:15:09.836Z │ +├──────────────────────────────────────┼──────────┼───────┼──────────────────────────┤ +│ 2056caf6-25d7-4ce8-a633-d10a8746d5dd │ Finished │ pythx │ 2019-09-10T17:12:42.341Z │ +├──────────────────────────────────────┼──────────┼───────┼──────────────────────────┤ +│ 63eb5611-ba4b-46e8-9e40-f735a0b86fd9 │ Finished │ pythx │ 2019-09-10T17:12:41.645Z │ +╘══════════════════════════════════════╧══════════╧═══════╧══════════════════════════╛ diff --git a/tests/testdata/analysis-status-simple.txt b/tests/testdata/analysis-status-simple.txt index 01aa140..1302450 100644 --- a/tests/testdata/analysis-status-simple.txt +++ b/tests/testdata/analysis-status-simple.txt @@ -1,4 +1,4 @@ UUID: ab9092f7-54d0-480f-9b63-1bb1508280e2 -Submitted at: 2019-09-05 20:34:27.606000+00:00 +Submitted at: 2019-09-05T20:34:27.606Z Status: Finished diff --git a/tests/testdata/analysis-status-table.txt b/tests/testdata/analysis-status-table.txt index cecd6c8..3af9ae9 100644 --- a/tests/testdata/analysis-status-table.txt +++ b/tests/testdata/analysis-status-table.txt @@ -1,35 +1,29 @@ -╒════════════════════╤═══════════════════════════════════════════════╕ -│ uuid │ ab9092f7-54d0-480f-9b63-1bb1508280e2 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ apiVersion │ v1.4.33-1-g1a235db │ -├────────────────────┼───────────────────────────────────────────────┤ -│ mythrilVersion │ 0.21.14 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ harveyVersion │ 0.0.34 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ maruVersion │ 0.5.4 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ queueTime │ 507 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ runTime │ 30307 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ status │ Finished │ -├────────────────────┼───────────────────────────────────────────────┤ -│ submittedAt │ 2019-09-05T20:34:27.606Z │ -├────────────────────┼───────────────────────────────────────────────┤ -│ submittedBy │ 5d6fca7fef1fc700129b6efa │ -├────────────────────┼───────────────────────────────────────────────┤ -│ mainSource │ test.sol │ -├────────────────────┼───────────────────────────────────────────────┤ -│ numSources │ 0 │ -├────────────────────┼───────────────────────────────────────────────┤ -│ numVulnerabilities │ {'high': 0, 'medium': 0, 'low': 0, 'none': 0} │ -├────────────────────┼───────────────────────────────────────────────┤ -│ clientToolName │ pythx │ -├────────────────────┼───────────────────────────────────────────────┤ -│ analysisMode │ full │ -├────────────────────┼───────────────────────────────────────────────┤ -│ groupName │ test-group │ -├────────────────────┼───────────────────────────────────────────────┤ -│ groupId │ 5dc410095e78890011fba1a4 │ -╘════════════════════╧═══════════════════════════════════════════════╛ +╒═════════════════╤══════════════════════════════════════╕ +│ UUID │ ab9092f7-54d0-480f-9b63-1bb1508280e2 │ +├─────────────────┼──────────────────────────────────────┤ +│ API Version │ v1.4.33-1-g1a235db │ +├─────────────────┼──────────────────────────────────────┤ +│ Mythril Version │ 0.21.14 │ +├─────────────────┼──────────────────────────────────────┤ +│ Harvey Version │ 0.0.34 │ +├─────────────────┼──────────────────────────────────────┤ +│ Maru Version │ 0.5.4 │ +├─────────────────┼──────────────────────────────────────┤ +│ Queue Time │ 507 │ +├─────────────────┼──────────────────────────────────────┤ +│ Run Time │ 30307 │ +├─────────────────┼──────────────────────────────────────┤ +│ Status │ Finished │ +├─────────────────┼──────────────────────────────────────┤ +│ Submitted At │ 2019-09-05T20:34:27.606Z │ +├─────────────────┼──────────────────────────────────────┤ +│ Submitted By │ 5d6fca7fef1fc700129b6efa │ +├─────────────────┼──────────────────────────────────────┤ +│ Tool Name │ pythx │ +├─────────────────┼──────────────────────────────────────┤ +│ Group ID │ 5dc410095e78890011fba1a4 │ +├─────────────────┼──────────────────────────────────────┤ +│ Group Name │ test-group │ +├─────────────────┼──────────────────────────────────────┤ +│ Analysis Mode │ full │ +╘═════════════════╧══════════════════════════════════════╛ diff --git a/tests/testdata/group-list-response.json b/tests/testdata/group-list-response.json index 9043b54..c04e30a 100644 --- a/tests/testdata/group-list-response.json +++ b/tests/testdata/group-list-response.json @@ -32,7 +32,7 @@ "name": "test", "createdAt": "2019-11-19T15:39:17.714Z", "createdBy": "5cefb9a4f50eaf001757f57d", - "completedAt": null, + "completedAt": "2019-11-19T15:39:17.714Z", "progress": 100, "mainSourceFiles": [], "status": "sealed", @@ -55,7 +55,7 @@ "name": "", "createdAt": "2019-11-19T15:23:35.258Z", "createdBy": "5cefb9a4f50eaf001757f57d", - "completedAt": null, + "completedAt": "2019-11-19T15:39:17.714Z", "progress": 100, "mainSourceFiles": [], "status": "sealed", @@ -78,7 +78,7 @@ "name": "", "createdAt": "2019-11-19T15:23:02.506Z", "createdBy": "5cefb9a4f50eaf001757f57d", - "completedAt": null, + "completedAt": "2019-11-19T15:39:17.714Z", "progress": 100, "mainSourceFiles": [], "status": "sealed", @@ -101,7 +101,7 @@ "name": "", "createdAt": "2019-11-19T15:22:57.831Z", "createdBy": "5cefb9a4f50eaf001757f57d", - "completedAt": null, + "completedAt": "2019-11-19T15:39:17.714Z", "progress": 100, "mainSourceFiles": [], "status": "sealed", diff --git a/tests/testdata/group-list-simple.txt b/tests/testdata/group-list-simple.txt index 401ca85..0714f61 100644 --- a/tests/testdata/group-list-simple.txt +++ b/tests/testdata/group-list-simple.txt @@ -1,25 +1,25 @@ ID: 5dd415a72ef82f0012f2e3a0 Name: -Created on: 2019-11-19 16:17:43.426000+00:00 +Created on: 2019-11-19T16:17:43.426Z Status: opened ID: 5dd40ca50d861d001101e888 Name: test -Created on: 2019-11-19 15:39:17.714000+00:00 +Created on: 2019-11-19T15:39:17.714Z Status: sealed ID: 5dd408f7b1380a0011e8a166 Name: -Created on: 2019-11-19 15:23:35.258000+00:00 +Created on: 2019-11-19T15:23:35.258Z Status: sealed ID: 5dd408d6519bf1001254baf2 Name: -Created on: 2019-11-19 15:23:02.506000+00:00 +Created on: 2019-11-19T15:23:02.506Z Status: sealed ID: 5dd408d1b1380a0011e8a163 Name: -Created on: 2019-11-19 15:22:57.831000+00:00 +Created on: 2019-11-19T15:22:57.831Z Status: sealed diff --git a/tests/testdata/group-list-table.txt b/tests/testdata/group-list-table.txt index be6573f..1c62240 100644 --- a/tests/testdata/group-list-table.txt +++ b/tests/testdata/group-list-table.txt @@ -1,11 +1,11 @@ ╒══════════════════════════╤════════╤══════════════════════════════════════════════════════════╤══════════════════════════╕ -│ 5dd415a72ef82f0012f2e3a0 │ opened │ BECToken.sol,remythx-mbt385.sol,functiontypes-swc127.sol │ 2019-11-19 16:17:43+0000 │ +│ 5dd415a72ef82f0012f2e3a0 │ opened │ BECToken.sol,remythx-mbt385.sol,functiontypes-swc127.sol │ 2019-11-19T16:17:43.426Z │ ├──────────────────────────┼────────┼──────────────────────────────────────────────────────────┼──────────────────────────┤ -│ 5dd40ca50d861d001101e888 │ sealed │ │ 2019-11-19 15:39:17+0000 │ +│ 5dd40ca50d861d001101e888 │ sealed │ │ 2019-11-19T15:39:17.714Z │ ├──────────────────────────┼────────┼──────────────────────────────────────────────────────────┼──────────────────────────┤ -│ 5dd408f7b1380a0011e8a166 │ sealed │ │ 2019-11-19 15:23:35+0000 │ +│ 5dd408f7b1380a0011e8a166 │ sealed │ │ 2019-11-19T15:23:35.258Z │ ├──────────────────────────┼────────┼──────────────────────────────────────────────────────────┼──────────────────────────┤ -│ 5dd408d6519bf1001254baf2 │ sealed │ │ 2019-11-19 15:23:02+0000 │ +│ 5dd408d6519bf1001254baf2 │ sealed │ │ 2019-11-19T15:23:02.506Z │ ├──────────────────────────┼────────┼──────────────────────────────────────────────────────────┼──────────────────────────┤ -│ 5dd408d1b1380a0011e8a163 │ sealed │ │ 2019-11-19 15:22:57+0000 │ +│ 5dd408d1b1380a0011e8a163 │ sealed │ │ 2019-11-19T15:22:57.831Z │ ╘══════════════════════════╧════════╧══════════════════════════════════════════════════════════╧══════════════════════════╛ diff --git a/tests/testdata/group-status-response.json b/tests/testdata/group-status-response.json index 2fb87b0..8d2fd17 100644 --- a/tests/testdata/group-status-response.json +++ b/tests/testdata/group-status-response.json @@ -3,7 +3,7 @@ "name": "test", "createdAt": "2019-11-19T15:39:17.714Z", "createdBy": "5cefb9a4f50eaf001757f57d", - "completedAt": null, + "completedAt": "2019-09-05T20:34:27.606Z", "progress": 100, "mainSourceFiles": [], "status": "sealed", diff --git a/tests/testdata/group-status-simple.txt b/tests/testdata/group-status-simple.txt index 29540ee..8cea791 100644 --- a/tests/testdata/group-status-simple.txt +++ b/tests/testdata/group-status-simple.txt @@ -1,5 +1,5 @@ ID: 5dd40ca50d861d001101e888 Name: test -Created on: 2019-11-19 15:39:17.714000+00:00 +Created on: 2019-11-19T15:39:17.714Z Status: sealed diff --git a/tests/testdata/group-status-table.txt b/tests/testdata/group-status-table.txt index c42cbaa..fcfd34e 100644 --- a/tests/testdata/group-status-table.txt +++ b/tests/testdata/group-status-table.txt @@ -3,7 +3,7 @@ ├──────────────────────────────────┼──────────────────────────┤ │ Name │ test │ ├──────────────────────────────────┼──────────────────────────┤ -│ Creation Date │ 2019-11-19 15:39:17+0000 │ +│ Creation Date │ 2019-11-19T15:39:17.714Z │ ├──────────────────────────────────┼──────────────────────────┤ │ Created By │ 5cefb9a4f50eaf001757f57d │ ├──────────────────────────────────┼──────────────────────────┤ diff --git a/tests/testdata/version-table.txt b/tests/testdata/version-table.txt index 2146c6f..56e42c7 100644 --- a/tests/testdata/version-table.txt +++ b/tests/testdata/version-table.txt @@ -1,11 +1,11 @@ ╒═════════╤══════════════════════════════════╕ │ Api │ v1.4.34.4 │ ├─────────┼──────────────────────────────────┤ -│ Maru │ 0.5.3 │ -├─────────┼──────────────────────────────────┤ -│ Mythril │ 0.21.14 │ +│ Hash │ 6e0035da873e809e90eab4665e3d19d6 │ ├─────────┼──────────────────────────────────┤ │ Harvey │ 0.0.33 │ ├─────────┼──────────────────────────────────┤ -│ Hash │ 6e0035da873e809e90eab4665e3d19d6 │ +│ Maru │ 0.5.3 │ +├─────────┼──────────────────────────────────┤ +│ Mythril │ 0.21.14 │ ╘═════════╧══════════════════════════════════╛ diff --git a/tox.ini b/tox.ini index d9b5174..8ba6a27 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,12 @@ [tox] envlist = py36, py37, py38, pypy3, lint, doctest -[travis] +[gh-actions] python = - pypy3: pypy3 - 3.8: p38 - 3.7: py37 3.6: py36 + 3.7: py37 + 3.8: py38, lint, doctest + pypy3: pypy3 [testenv:lint] basepython = python From dfb5a41adf4a3f00542951fb40916e881fe49009 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Fri, 12 Feb 2021 14:27:55 +0100 Subject: [PATCH 2/2] Bump pythx version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c1a4af7..8e42963 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==7.1.2 py-solc-x==1.1.0 -pythx==1.6.2 +pythx==1.7.1 tabulate==0.8.7 Jinja2==2.11.3 htmlmin==0.1.12