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

Implement version hash #631

Merged
merged 12 commits into from
Dec 28, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- #627 Fix parsing of octal literal (@lieryan)
- #611 Implement JSON DataFile serialization (@lieryan)
- #630 SQLite models improvements (@lieryan)
- #631 Implement version hash (@lieryan)


# Release 1.6.0
Expand Down
43 changes: 43 additions & 0 deletions rope/base/versioning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import hashlib
import importlib.util
import json
from typing import Dict

import rope.base.project


def get_version_hash_data(project: rope.base.project.Project) -> Dict[str, str]:
version_hash_data = dict(
version_data=f"{rope.VERSION}",
prefs_data=_get_prefs_data(project),
schema_file_content=_get_file_content("rope.contrib.autoimport.models"),
)
return version_hash_data


def calculate_version_hash(project: rope.base.project.Project) -> str:
def _merge(hasher, name: str, serialized_data: str):
hashed_data = hashlib.sha256(serialized_data.encode("utf-8")).hexdigest()
hasher.update(hashed_data.encode("ascii"))

hasher = hashlib.sha256()
for name, data in get_version_hash_data(project).items():
_merge(hasher, name, data)
return hasher.hexdigest()


def _get_prefs_data(project) -> str:
prefs_data = dict(vars(project.prefs))
del prefs_data["project_opened"]
del prefs_data["callbacks"]
del prefs_data["dependencies"]
return json.dumps(prefs_data, sort_keys=True, indent=2)


def _get_file_content(module_name: str) -> str:
models_module = importlib.util.find_spec(module_name)
assert models_module and models_module.loader
assert isinstance(models_module.loader, importlib.machinery.SourceFileLoader)
src = models_module.loader.get_source(module_name)
assert src
return src
40 changes: 40 additions & 0 deletions ropetest/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pathlib

import pytest

from rope.base import resources
from ropetest import testutils


@pytest.fixture
def project():
project = testutils.sample_project()
yield project
testutils.remove_project(project)


@pytest.fixture
def project_path(project):
yield pathlib.Path(project.address)


"""
Standard project structure for pytest fixtures
/mod1.py -- mod1
/pkg1/__init__.py -- pkg1
/pkg1/mod2.py -- mod2
"""

@pytest.fixture
def mod1(project) -> resources.File:
return testutils.create_module(project, "mod1")


@pytest.fixture
def pkg1(project) -> resources.Folder:
return testutils.create_package(project, "pkg1")


@pytest.fixture
def mod2(project, pkg1) -> resources.Folder:
return testutils.create_module(project, "mod2", pkg1)
12 changes: 0 additions & 12 deletions ropetest/contrib/autoimport/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@
from ropetest import testutils


@pytest.fixture
def project():
project = testutils.sample_project()
yield project
testutils.remove_project(project)


@pytest.fixture
def mod1(project):
mod1 = testutils.create_module(project, "mod1")
Expand All @@ -23,11 +16,6 @@ def mod1_path(mod1):
yield pathlib.Path(mod1.real_path)


@pytest.fixture
def project_path(project):
yield pathlib.Path(project.address)


@pytest.fixture
def typing_path():
import typing
Expand Down
37 changes: 37 additions & 0 deletions ropetest/versioningtest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import secrets
from unittest.mock import patch

from rope.base import versioning


def test_calculate_version_hash(project):
version_hash = versioning.calculate_version_hash(project)
assert isinstance(version_hash, str)


def test_version_hash_is_constant(project):
version_hash_1 = versioning.calculate_version_hash(project)
version_hash_2 = versioning.calculate_version_hash(project)
assert version_hash_1 == version_hash_2


def test_version_hash_varies_on_rope_version(project):
actual_version_hash = versioning.calculate_version_hash(project)
with patch("rope.VERSION", "1.0.0"):
patched_version_hash = versioning.calculate_version_hash(project)
assert actual_version_hash != patched_version_hash


def test_version_hash_varies_on_user_preferences(project):
actual_version_hash = versioning.calculate_version_hash(project)
assert project.prefs.get("automatic_soa") is False
project.prefs.set("automatic_soa", True)
patched_version_hash = versioning.calculate_version_hash(project)
assert actual_version_hash != patched_version_hash


def test_version_hash_varies_on_get_file_content(project):
actual_version_hash = versioning.calculate_version_hash(project)
with patch("rope.base.versioning._get_file_content", return_value=secrets.token_hex()):
patched_version_hash = versioning.calculate_version_hash(project)
assert actual_version_hash != patched_version_hash