Skip to content

Commit

Permalink
[clean] Tests + additional checks in manifest parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
lpascal-ledger committed Oct 18, 2023
1 parent bed2f0c commit d3a9bb0
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 19 deletions.
28 changes: 26 additions & 2 deletions .github/workflows/build_and_tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build, test and deploy Ragger
name: Build, test and deploy Ledgered

on:
workflow_dispatch:
Expand All @@ -14,9 +14,33 @@ on:
- develop

jobs:

build_install_test:
name: Build, install and test the Ledgered Python package
runs-on: ubuntu-latest
steps:

- name: Clone
uses: actions/checkout@v3

- name: Build & install
run: |
pip install -U pip
pip install -U .[dev]
- name: Run tests and generate coverage
run: pytest -v --tb=short tests/ --cov ledgered --cov-report xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
name: codecov-ledgered


package_and_deploy:
name: Build and deploy Ledgered Python Package
name: Build and deploy Ledgered Python package
runs-on: ubuntu-latest
needs: [build_install_test]
steps:

- name: Clone
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ dependencies = [
"toml",
]

[project.optional-dependencies]
dev = [
"pytest",
"pytest-cov"
]

[project.urls]
Home = "https://github.com/LedgerHQ/ledgered"

Expand Down
48 changes: 31 additions & 17 deletions src/ledgered/utils/manifest.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import logging
import sys
import toml
from argparse import ArgumentParser, Namespace
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Union

from toml import loads
from typing import Dict, IO, Iterable, Optional, Set, Union

MANIFEST_FILE_NAME = "ledger_app.toml"
EXISTING_DEVICES = ["nanos", "nanox", "nanos+", "stax"]


@dataclass
class TestsConfig:
__test__ = False # deactivate pytest discovery warning

unit_directory: Optional[Path]
pytest_directory: Optional[Path]

Expand All @@ -26,13 +28,19 @@ def __init__(self,
class AppConfig:
sdk: str
build_directory: Path
devices: List[str]
devices: Set[str]

def __init__(self, sdk: str, build_directory: Union[str, Path], devices: List[str]) -> None:
if not sdk.lower() in ["rust", "c"]:
def __init__(self, sdk: str, build_directory: Union[str, Path], devices: Iterable[str]) -> None:
sdk = sdk.lower()
if sdk not in ["rust", "c"]:
raise ValueError(f"'{sdk}' unknown. Must be either 'C' or 'Rust'")
self.sdk = sdk.lower()
self.sdk = sdk
self.build_directory = Path(build_directory)
devices = {device.lower() for device in devices}
unknown_devices = devices.difference(EXISTING_DEVICES)
if unknown_devices:
unknown_devices_str = "', '".join(unknown_devices)
raise ValueError(f"Unknown devices: '{unknown_devices_str}'")
self.devices = devices

@property
Expand All @@ -41,7 +49,7 @@ def is_rust(self) -> bool:

@property
def is_c(self) -> bool:
return True
return not self.is_rust


@dataclass
Expand All @@ -53,14 +61,21 @@ def __init__(self, app: Dict, tests: Optional[Dict] = None) -> None:
self.app = AppConfig(**app)
self.tests = None if tests is None else TestsConfig(**tests)

@staticmethod
def from_string(content: str) -> "RepoManifest":
return RepoManifest(**toml.loads(content))

@staticmethod
def from_io(manifest_io: IO) -> "RepoManifest":
return RepoManifest(**toml.load(manifest_io))

def parse_manifest(content: str, root: Path = Path(".")) -> RepoManifest:
"""
Parse the raw content of an application manifest ('ledger_app.toml') and
extract relevant information.
Returned value will depends on if the application is a C or a Rust one.
"""
return RepoManifest(**loads(content))
@staticmethod
def from_path(path: Path) -> "RepoManifest":
if path.is_dir():
path = path / MANIFEST_FILE_NAME
assert path.is_file(), f"'{path.resolve()}' is not a manifest file."
with path.open() as manifest_io:
return RepoManifest.from_io(manifest_io)


# CLI-oriented code #
Expand Down Expand Up @@ -113,8 +128,7 @@ def main():
args = parse_args()
assert args.manifest.is_file(), f"'{args.manifest.resolve()}' does not appear to be a file."
manifest = args.manifest.resolve()
with manifest.open() as filee:
repo_manifest = parse_manifest(filee.read())
repo_manifest = RepoManifest.from_path(manifest)

display_content = dict()
if args.output_sdk:
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/_data/ledger_app.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[app]
sdk = "Rust"
build_directory = "."
devices = ["nanos", "Stax"]

[tests]
unit_directory = "unit"
pytest_directory = "pytest"
Empty file added tests/unit/utils/__init__.py
Empty file.
82 changes: 82 additions & 0 deletions tests/unit/utils/test_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from pathlib import Path
from unittest import TestCase

from ledgered.utils.manifest import AppConfig, RepoManifest, TestsConfig, MANIFEST_FILE_NAME


TEST_MANIFEST_DIRECTORY = Path(__file__).parent.parent / "_data"


class TestAppConfig(TestCase):

def test___init__ok_complete(self):
sdk = "Rust"
bd = Path("some path")
devices = ["nanos", "NanoS+"]
config = AppConfig(sdk=sdk, build_directory=str(bd), devices=devices)
self.assertEqual(config.sdk, sdk.lower())
self.assertEqual(config.build_directory, bd)
self.assertEqual(config.devices, {device.lower() for device in devices})
self.assertTrue(config.is_rust)
self.assertFalse(config.is_c)

def test___init__nok_unknown_sdk(self):
with self.assertRaises(ValueError):
AppConfig(sdk="Java", build_directory=str(), devices=set())

def test___init__nok_unknown_device(self):
devices = {"hic sunt", "dracones"}
with self.assertRaises(ValueError) as error:
AppConfig(sdk="rust", build_directory=str(), devices=devices)
self.assertIn("', '".join(devices), str(error.exception))


class TestTestsConfig(TestCase):

def test___init__ok_complete(self):
ud = Path("")
pd = Path("something")
config = TestsConfig(unit_directory=str(ud), pytest_directory=str(pd))
self.assertIsNotNone(config.unit_directory)
self.assertEqual(config.unit_directory, ud)
self.assertIsNotNone(config.pytest_directory)
self.assertEqual(config.pytest_directory, pd)

def test__init__ok_empty(self):
config = TestsConfig(**dict())
self.assertIsNone(config.unit_directory)
self.assertIsNone(config.pytest_directory)


class TestRepoManifest(TestCase):

def check_ledger_app_toml(self, manifest: RepoManifest) -> None:
self.assertEqual(manifest.app.sdk, "rust")
self.assertEqual(manifest.app.devices, {"nanos", "stax"})
self.assertEqual(manifest.app.build_directory, Path())
self.assertTrue(manifest.app.is_rust)
self.assertFalse(manifest.app.is_c)

self.assertEqual(manifest.tests.unit_directory, Path("unit"))
self.assertEqual(manifest.tests.pytest_directory, Path("pytest"))

def test___init__ok(self):
app = {"sdk": "rust", "devices": ["NANOS", "stAX"], "build_directory": ""}
tests = {"unit_directory": "unit", "pytest_directory": "pytest"}
self.check_ledger_app_toml(RepoManifest(app, tests))

def test_from_path_ok(self):
self.check_ledger_app_toml(RepoManifest.from_path(TEST_MANIFEST_DIRECTORY))
self.check_ledger_app_toml(RepoManifest.from_path(TEST_MANIFEST_DIRECTORY / MANIFEST_FILE_NAME))

def test_from_path_nok(self):
with self.assertRaises(AssertionError):
RepoManifest.from_path(Path("/not/existing/path"))

def test_from_io_ok(self):
with (TEST_MANIFEST_DIRECTORY / MANIFEST_FILE_NAME).open() as manifest_io:
self.check_ledger_app_toml(RepoManifest.from_io(manifest_io))

def test_from_string_ok(self):
with (TEST_MANIFEST_DIRECTORY / MANIFEST_FILE_NAME).open() as manifest_io:
self.check_ledger_app_toml(RepoManifest.from_string(manifest_io.read()))

0 comments on commit d3a9bb0

Please sign in to comment.