Last active
October 5, 2021 17:09
-
-
Save ivdunin/e9f3e1a20f5ad7dff45a83c490f242c3 to your computer and use it in GitHub Desktop.
canonical_path
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
MAX_FILENAME_LENGTH = 255 | |
MAX_FILEPATH_LENGTH = 4096 | |
SEP = '/' | |
""" | |
Дана строка, которая является абсолютным путем к файлу или директории в системе Unix. | |
Нужно упростить эту строку до каноничного пути и написать автотесты для своей реализации. Использовать Python + pytest | |
""" | |
def canonical_path(path: str) -> str: | |
if sys.platform != "linux": | |
raise OSError('Works only on linux os') | |
canonical = [''] | |
if not path.strip(): | |
raise ValueError('Empty path value!') | |
if not path.startswith(SEP): | |
raise ValueError(f'Not an absolute path! Path should start with {SEP}') | |
if path.strip() == SEP: | |
return SEP | |
tokens = path.split(SEP) | |
for token in tokens: | |
if token and token != '.': | |
if token != '..': | |
if len(token) > MAX_FILENAME_LENGTH: | |
raise ValueError('File or directory name too long!') | |
canonical.append(f'{token}') | |
else: | |
del canonical[-1] | |
# there are few cases when linux realpath differs from python os.path.realpath behavior | |
# as I compare results with python implementation, I took Python approach | |
if not canonical: | |
canonical.append('') | |
if len(canonical) == 1: | |
return f'/{canonical[0]}' | |
else: | |
res = '/'.join(canonical) | |
if len(res) > MAX_FILEPATH_LENGTH: | |
raise ValueError('Resulted file path too long!') | |
else: | |
return res |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from math import ceil | |
from canonical_path import MAX_FILEPATH_LENGTH | |
def generate_long_path(length: int = MAX_FILEPATH_LENGTH) -> str: | |
prefix = '/home/user' | |
fn = '/{0}'.format('AbCdE' * 51) | |
mul = ceil((MAX_FILEPATH_LENGTH - len(prefix)) / len(fn)) | |
final_path = '{p}{f}'.format(p=prefix, f=fn * mul) | |
return final_path[:length] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import pytest | |
from canonical_path import canonical_path, MAX_FILENAME_LENGTH, MAX_FILEPATH_LENGTH | |
from lib import generate_long_path | |
MAX_FILE_NAME_PLUS = 'a' * (MAX_FILENAME_LENGTH + 1) | |
MAX_PATH_PLUS = generate_long_path(MAX_FILEPATH_LENGTH + 1) | |
@pytest.mark.parametrize( | |
'initial_path, msg', [ | |
pytest.param('user/data', 'Not an absolute path! Path should start with /', id='Valid path'), | |
pytest.param(' ', 'Empty path value!', id='Empty path'), | |
pytest.param('/home/data/{0}'.format(MAX_FILE_NAME_PLUS), 'File or directory name too long!', | |
id='Exceed linux file length'), | |
pytest.param(MAX_PATH_PLUS, 'Resulted file path too long!', id='Exceed linux path length') | |
] | |
) | |
def test_negative(initial_path, msg): | |
with pytest.raises(ValueError) as excinfo: | |
canonical_path(initial_path) | |
assert str(excinfo.value) == msg | |
def test_invalid_platform(monkeypatch): | |
monkeypatch.setattr(sys, 'platform', 'win32') | |
with pytest.raises(OSError) as excinfo: | |
canonical_path('c:/dir') | |
assert str(excinfo.value) == 'Works only on linux os' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from os import path | |
import pytest | |
from canonical_path import canonical_path, MAX_FILENAME_LENGTH | |
from lib import generate_long_path | |
MAX_FILE_NAME = 'a' * MAX_FILENAME_LENGTH | |
MAX_PATH = generate_long_path() | |
@pytest.mark.parametrize( | |
'initial_path', [ | |
pytest.param('/', id='Root path'), | |
pytest.param('//', id='Root path with double /'), | |
pytest.param('/../data', id='Root dir'), | |
pytest.param('/home/data/file.txt', id='Valid path'), | |
pytest.param('/home/data///dir/', id='Path ends with /'), | |
pytest.param('/home/data/../data.txt', id='Path contains multiple dots'), | |
pytest.param('/home/data/{0}'.format(MAX_FILE_NAME), id='Max unix filename length'), | |
pytest.param(MAX_PATH, id='Max unix filepath length'), | |
pytest.param(f'///{MAX_PATH}', id='Resulted filepath less or equal to limit'), | |
pytest.param('/home/user/../test/../../../../data/text.txt', id='Multiple dots in path'), | |
pytest.param('/home/user/././text.file', id='file with dot dot') | |
] | |
) | |
def test_positive(initial_path): | |
assert canonical_path(initial_path) == path.realpath(initial_path) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment