Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ruff
b52f9071f50b11058dd8bd689281c52cbfb6c5f2
1 change: 0 additions & 1 deletion .github/workflows/build_and_functional_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,3 @@ jobs:
with:
download_app_binaries_artifact: "app_tezos_binaries"
regenerate_snapshots: ${{ github.event_name == 'workflow_dispatch' && inputs.golden_run == 'Open a PR' }}
test_dir: "tests/standalone"
1 change: 0 additions & 1 deletion .github/workflows/coding_style_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ jobs:
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1
with:
source: './app/src'
extensions: 'h,c'
10 changes: 4 additions & 6 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.10'
- run: python -m pip install pre-commit
- run: python -m pip freeze --local
- run: SKIP=clang-format,pylint pre-commit run --all-files
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
1 change: 0 additions & 1 deletion .github/workflows/python_client_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ jobs:
name: Call Ledger Python linters
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_python_checks.yml@v1
with:
run_linter: pylint
run_type_check: true
src_directory: .
setup_directory: tests/standalone
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ jobs:
job_unit_test:
name: Call Ledger unit_test
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_unit_tests.yml@v1
secrets: inherit
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
codecov_token: ${{ secrets.CODECOV_TOKEN }}
46 changes: 31 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,40 @@ repos:
rev: v6.0.0
hooks:
- id: trailing-whitespace
args: ['--markdown-linebreak-ext=md']
- id: end-of-file-fixer
- id: mixed-line-ending
- id: check-yaml
- id: check-added-large-files
- repo: local
- id: check-merge-conflict
- id: check-case-conflict

- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
- id: codespell
args: ['--ignore-words-list', 'cips', '3sT']

- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v21.1.8
hooks:
- id: clang-format
name: clang-format
description: Run `clang-format` on C/C++/CUDA files.
entry: clang-format -i
language: system
files: ^
types: [file, c]
types_or: [c]

- id: pylint
name: pylint
description: Run pylint with custom .pylintrc configuration
entry: pylint
language: system
pass_filenames: false
args: ["--errors-only", "--rcfile=tests/integration/.pylintrc", "tests/standalone/"]
types: [python]
- repo: https://github.com/Mateusz-Grzelinski/actionlint-py
rev: v1.7.12.24
hooks:
- id: actionlint
types_or: [yaml]
args: [-shellcheck="" -pyflakes=""]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.20
hooks:
# Run the linter.
- id: ruff-check
types_or: [ python, pyi ]
args: [ --fix ]
# Run the formatter.
- id: ruff-format
types_or: [ python, pyi ]
3 changes: 1 addition & 2 deletions app/src/format.c → app/src/app_format.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
See the License for the specific language governing permissions and
limitations under the License. */

#include <format.h>

#include "format.h"
#include "app_format.h"

bool
tz_mutez_to_string(char *obuf, size_t olen, uint64_t amount)
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion app/src/handler/sign.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#include <swap.h>
#endif

#include "../format.h"
#include "app_format.h"
#include "globals.h"
#include "handle_swap.h"
#include "keys.h"
Expand Down
2 changes: 2 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mypy]
ignore_missing_imports = True
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
testpaths = tests/functional
pythonpath = tests/functional
20 changes: 20 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
target-version = "py311"
line-length = 130

# Vendored third-party packages: not our code to lint.
exclude = [
"deps/"
]

[lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"B", # bugbear
"UP", # pyupgrade
"RUF", # ruff-native rules
]

ignore = [
]
4 changes: 1 addition & 3 deletions scripts/gen_fa2_ctest_hex.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,7 @@ def main() -> None:
print(f' "{forge_batch_hex(_FA2_NEG)}"')
print()
print("/* FA2 clear-signing (token_id=1, wrapped token registry): */")
print(
f' "{forge_batch_hex(_FA2_TRANSFER_TOKEN_ID_GT0_PARAMETER, "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ")}"'
)
print(f' "{forge_batch_hex(_FA2_TRANSFER_TOKEN_ID_GT0_PARAMETER, "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ")}"')


if __name__ == "__main__":
Expand Down
140 changes: 76 additions & 64 deletions tests/integration/check_section_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,129 +15,141 @@

import argparse
import os
import requests
from dataclasses import dataclass
import time
from dataclasses import dataclass

import requests

TIMEOUT = int(os.environ.get("TIMEOUT", "5"))

TIMEOUT = int(os.environ.get('TIMEOUT', '5'))

@dataclass
class Screen:
title: str
text: list[str]

def __str__(self):
return f"title=\"{self.title}\" Text=[{', '.join(self.text)}]"

def matches(self, content: str, content_lines: int) -> bool:
for l in self.text:
content = content.lstrip('\n')
print(f"l: {l}, c: {content[:len(l)]}")
if not content.startswith(l):
return False
content = content.removeprefix(l)
content = content.lstrip('\n')

return True

def strip(self, content: str) -> str:
for l in self.text:
content = content.lstrip('\n')
content = content.removeprefix(l)
content = content.lstrip('\n')
return content

def with_retry(f, attempts= (2 * TIMEOUT)):
title: str
text: list[str]

def __str__(self):
return f'title="{self.title}" Text=[{", ".join(self.text)}]'

def matches(self, content: str, content_lines: int) -> bool:
for length in self.text:
content = content.lstrip("\n")
print(f"l: {length}, c: {content[: len(length)]}")
if not content.startswith(length):
return False
content = content.removeprefix(length)
content = content.lstrip("\n")

return True

def strip(self, content: str) -> str:
for length in self.text:
content = content.lstrip("\n")
content = content.removeprefix(length)
content = content.lstrip("\n")
return content


def with_retry(f, attempts=(2 * TIMEOUT)):
while True:
try:
return f()
except AssertionError as e:
print('- retrying:', e)
print("- retrying:", e)
if attempts <= 0:
raise e
attempts -= 1
time.sleep(0.5)


def get_screen(url):
"""Retrieve the current screen title & contents"""
r = requests.get(f'{url}/events', params = {"currentscreenonly":"true"})
r = requests.get(f"{url}/events", params={"currentscreenonly": "true"})
r.raise_for_status()
lines = [e["text"] for e in r.json()["events"]]

assert len(lines) > 0, "Unexpected empty screen. Speculos killed?"

screen = Screen(title=lines[0], text=lines[1:])
print(f'- {screen} -')
print(f"- {screen} -")
return screen


def find_last(lst, elm):
"""Find the index of the last element in lst that matches elm"""
gen = (len(lst) - 1 - i for i, v in enumerate(reversed(lst)) if v == elm)
return next(gen, None)
"""Find the index of the last element in lst that matches elm"""
gen = (len(lst) - 1 - i for i, v in enumerate(reversed(lst)) if v == elm)
return next(gen, None)


def get_titled_screen(url, title):
"""Retrieve the screen until title"""
r = requests.get(f'{url}/events')
r = requests.get(f"{url}/events")
r.raise_for_status()
lines = [e["text"] for e in r.json()["events"]]

assert len(lines) > 0, "Unexpected empty screen. Speculos killed?"

assert title in lines, f"Title \"{title}\" no found"
assert title in lines, f'Title "{title}" no found'

title_index = find_last(lines, title)

screen = Screen(title=lines[title_index], text=lines[title_index+1:])
print(f'- {screen} -')
screen = Screen(title=lines[title_index], text=lines[title_index + 1 :])
print(f"- {screen} -")
return screen


def press_button(url, button):
"""Press a button on the ledger device."""
print("- press", button)
r = requests.post(f'{url}/button/{button}', json = {"action": "press-and-release"})
r = requests.post(f"{url}/button/{button}", json={"action": "press-and-release"})
r.raise_for_status()


def press_right(url):
"""Press the right button."""
return press_button(url, 'right')
return press_button(url, "right")


def press_left(url):
"""Press the left button."""
return press_button(url, 'left')
return press_button(url, "left")


def check_multi_screen(url, title, content, content_lines, device):
"""Assert that the screen contents across all screens with the given title match expected content."""
while True:
def check_screen():
if device in ["nanos", "nanosp"]:
screen = get_titled_screen(url, title)
assert screen.title == title, f"expected section '{title}' but on '{screen.title}'"
# https://github.com/trilitech/ledger-app-tezos-wallet/issues/43
# Get screens contents with the 'events' service for nanox
# while the 'events?currentscreenonly=true' service does not
# work properly for nanox in the sha-6a34680 version of
# speculos
if device == "nanox":
screen = get_titled_screen(url, title)
assert screen.matches(content, content_lines), \
f"{screen} did not match {content[:10]}...{content[-10:]}"

return screen.strip(content)
def check_screen(content=content):
if device in ["nanos", "nanosp"]:
screen = get_titled_screen(url, title)
assert screen.title == title, f"expected section '{title}' but on '{screen.title}'"
# https://github.com/trilitech/ledger-app-tezos-wallet/issues/43
# Get screens contents with the 'events' service for nanox
# while the 'events?currentscreenonly=true' service does not
# work properly for nanox in the sha-6a34680 version of
# speculos
if device == "nanox":
screen = get_titled_screen(url, title)
assert screen.matches(content, content_lines), f"{screen} did not match {content[:10]}...{content[-10:]}"

return screen.strip(content)

content = with_retry(check_screen)
content = with_retry(check_screen)

if content == "":
break
if content == "":
break

press_right(url)

press_right(url)

def device_content_lines(device: str) -> int:
if device == "nanos":
return 1
if device in ["nanosp", "nanox"]:
return 4
if device == "nanos":
return 1
if device in ["nanosp", "nanox"]:
return 4

raise ValueError(f"unsupported device '{device}'")

raise ValueError(f"unsupported device '{device}'")

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Check a section of pages contain the expected content.")
Expand Down
Loading
Loading