Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Unit tests for serialize_v0 file model #58

Merged
merged 4 commits into from
Oct 30, 2023
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
32 changes: 32 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Run unit tests
on:
pull_request:
branches: [main]
types: [opened, synchronize]

permissions: {}

jobs:
model-signing-unit-tests:
name: Run unit tests for signing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1
with:
python-version: 3.11
cache: pip
cache-dependency-path: |
model_signing/install/requirements.txt
model_signing/install/requirements.test.txt
- name: Install dependencies
run: |
set -euo pipefail
python -m venv venv
source venv/bin/activate
python -m pip install --require-hashes -r model_signing/install/requirements.txt
python -m pip install --require-hashes -r model_signing/install/requirements.test.txt
- name: Run unit tests
run: |
set -euo pipefail
pytest .
22 changes: 22 additions & 0 deletions model_signing/install/requirements.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --output-file=install/requirements.test.txt install/requirements.test.in
#
iniconfig==2.0.0 \
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
# via pytest
packaging==23.2 \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
# via pytest
pluggy==1.3.0 \
--hash=sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12 \
--hash=sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7
# via pytest
pytest==7.4.3 \
--hash=sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac \
--hash=sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5
# via -r install/requirements.test.in
4 changes: 2 additions & 2 deletions model_signing/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def task(task_info: []):
def serialize_v1(path: Path, chunk: int, signature_path: Path,
ignorepaths: [Path] = []) -> bytes:
if not path.exists():
raise ValueError(f"{str(path)} does not eixst")
raise ValueError(f"{str(path)} does not exist")

if not allow_symlinks and path.is_symlink():
raise ValueError(f"{str(path)} is a symlink")
Expand Down Expand Up @@ -312,7 +312,7 @@ def serialize_v1(path: Path, chunk: int, signature_path: Path,
def serialize_v0(path: Path, chunk: int, signature_path: Path,
ignorepaths: [Path] = []) -> bytes:
if not path.exists():
raise ValueError(f"{str(path)} does not eixst")
raise ValueError(f"{str(path)} does not exist")

if not allow_symlinks and path.is_symlink():
raise ValueError(f"{str(path)} is a symlink")
Expand Down
140 changes: 140 additions & 0 deletions model_signing/serialize_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import os
from pathlib import Path
import pytest
from serialize import Serializer
import shutil


testdata_dir = "testdata"


# Utility functions.
def create_folder(name: str) -> Path:
p = os.path.join(os.getcwd(), testdata_dir, name)
os.makedirs(p)
return Path(p)


def create_symlinks(src: str, dst: str) -> Path:
psrc = os.path.join(os.getcwd(), testdata_dir, src)
pdst = os.path.join(os.getcwd(), testdata_dir, dst)
os.symlink(psrc, pdst)
return Path(dst)


def cleanup_model(p: Path) -> None:
if p.is_dir():
shutil.rmtree(p)
elif p.is_file():
os.unlink(p)
try:
os.unlink(p.with_suffix(".sig"))
except FileNotFoundError:
pass


def create_file(name: str, data: bytes) -> Path:
p = os.path.join(os.getcwd(), testdata_dir, name)
with open(p, "wb") as f:
f.write(data)
return Path(p)


def create_random_file(name: str, size: int) -> (Path, bytes):
p = os.path.join(os.getcwd(), testdata_dir, name)
content = os.urandom(size)
with open(p, "wb") as f:
f.write(content)
return Path(p), content


def signature_path(model: Path) -> Path:
if model.is_file():
return model.with_suffix(".sig")
return model.joinpath("model.sig")


class Test_serialize_v0:
# symlink in root folder raises ValueError exception.
def test_symlink_root(self):
folder = "with_root_symlinks"
model = create_folder(folder)
sig = signature_path(model)
create_symlinks(".", os.path.join(folder, "root_link"))
with pytest.raises(ValueError):
Serializer.serialize_v0(Path(folder), 0, sig)
cleanup_model(model)

# symlink in non-root folder raises ValueError exception.
def test_symlink_nonroot(self):
model = create_folder("with_nonroot_symlinks")
sub_folder = model.joinpath("sub")
create_folder(str(sub_folder))
sig = signature_path(model)
create_symlinks(".", os.path.join(sub_folder, "sub_link"))
with pytest.raises(ValueError):
Serializer.serialize_v0(model, 0, sig)
cleanup_model(model)

# File serialization works.
def test_known_file(self):
file = "model_file"
data = b"hellow world content"
model = create_file(file, data)
sig_path = signature_path(model)
expected = b'x\x9d\xa4N\x9f\xeajd\xd8\x87\x84\x1a\xd3\xb3\xfc\xeb\xf6\r\x01\x9fi8#\xd8qU\x90\xca\x9d\x83\xe1\x8b' # noqa: E501 ignore long line warning
computed = Serializer.serialize_v0(model, 0, sig_path)
assert (computed == expected)
cleanup_model(model)

# File serialization returns the same results for different chunk sizes.
def test_file_chuncks(self):
file = "model_file"
file_size = 999
model, _ = create_random_file(file, file_size)
sig_path = signature_path(model)
result = Serializer.serialize_v0(model, 0, sig_path)
# NOTE: we want to also test a chunk size larger than the files size.
for c in range(1, file_size + 1):
r = Serializer.serialize_v0(model, c, sig_path)
assert (r == result)
cleanup_model(model)

# File serialization returns the same results for different file names.
def test_different_filename(self):
file = "model_file"
data = b"hellow world content"
model = create_file(file, data)
sig_path = signature_path(model)
r0 = Serializer.serialize_v0(model, 0, sig_path)
cleanup_model(model)

file = "model_file2"
model = create_file(file, data)
sig_path = signature_path(model)
r1 = Serializer.serialize_v0(model, 0, sig_path)
cleanup_model(model)

assert (r0 == r1)

# File serialization returns a different result for different model
# contents.
def test_altered_file(self):
file = "model_file"
file_size = 999
model, content = create_random_file(file, file_size)
sig_path = signature_path(model)
result = Serializer.serialize_v0(model, 0, sig_path)
for c in range(file_size):
altered_content = content[:c] + bytes([content[c] | 1]) + \
content[c:]
altered_file = file + (".%d" % c)
altered_model = create_file(altered_file, altered_content)
altered_sig_path = signature_path(altered_model)
altered_result = Serializer.serialize_v0(altered_model, 0,
altered_sig_path)
assert (altered_result != result)
cleanup_model(altered_model)
cleanup_model(model)

# TODO(#57): directory support.
Loading