diff --git a/checker/actions/grade.py b/checker/actions/grade.py index cfa835b..5b6f877 100644 --- a/checker/actions/grade.py +++ b/checker/actions/grade.py @@ -3,11 +3,14 @@ import os import subprocess import sys +import tempfile from datetime import datetime +from pathlib import Path from ..course import CourseConfig, CourseDriver, CourseSchedule, Group, Task from ..exceptions import RunFailedError from ..testers import Tester +from ..utils import get_folders_diff_except_public, get_tracked_files_list from ..utils.manytask import PushFailedError, push_report from ..utils.print import print_info, print_task_info @@ -42,8 +45,6 @@ def _get_git_changes( print_info(f'CI_COMMIT_SHA: {current_commit_sha}, CI_COMMIT_BEFORE_SHA: {prev_commit_sha}!') git_changes_type = 'diff_last' - print_info('Loading changes...', color='orange') - changes = [] if git_changes_type.startswith('diff'): if git_changes_type == 'diff_between': @@ -112,6 +113,7 @@ def _get_git_changes( result = subprocess.run( f'cd {solution_root} && ' + f'git fetch --unshallow &&' f'(git remote rm upstream | true) &&' f'git remote add upstream {public_repo_url}.git &&' f'git fetch upstream', @@ -250,6 +252,91 @@ def grade_tasks( return success +def _get_changes_using_real_folders( + course_config: CourseConfig, + current_folder: str, + old_hash: str, + current_repo_gitlab_path: str, + gitlab_token: str, +) -> list[str]: + gitlab_url_with_token = course_config.gitlab_url.replace('://', f'://gitlab-ci-token:{gitlab_token}@') + + with tempfile.TemporaryDirectory() as public_dir: + with tempfile.TemporaryDirectory() as old_dir: + # download public repo, minimal + print_info(f'Cloning {course_config.public_repo} of {course_config.default_branch}...', color='white') + # print_info('git clone:', color='grey') + subprocess.run( + f'git clone --depth=1 --branch={course_config.default_branch} ' + f'{course_config.gitlab_url}/{course_config.public_repo}.git {public_dir}', + encoding='utf-8', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + ) + # print_info(r.stdout, color='grey') + # print_info(f'ls -lah {public_dir}', color='grey') + subprocess.run( + f'ls -lah {public_dir}', + encoding='utf-8', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + ) + # print_info(r.stdout, color='grey') + + # download old repo by hash, minimal + print_info(f'Cloning {current_repo_gitlab_path} to get {old_hash}...', color='white') + # print_info('git clone:', color='grey') + subprocess.run( + f'git clone --depth=1 --branch={course_config.default_branch} ' + f'{gitlab_url_with_token}/{current_repo_gitlab_path}.git {old_dir}', + encoding='utf-8', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + ) + # print_info(r.stdout, color='grey') + # print_info(f'git fetch origin {old_hash} && git checkout FETCH_HEAD:', color='grey') + subprocess.run( + f'git fetch origin {old_hash} && git checkout FETCH_HEAD', + encoding='utf-8', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + cwd=old_dir, + ) + # print_info(r.stdout, color='grey') + # print_info(f'ls -lah {old_dir}', color='grey') + subprocess.run( + f'ls -lah {old_dir}', + encoding='utf-8', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + ) + # print_info(r.stdout, color='grey') + + # get diff + print_info('Detected changes (filtering by public repo and git tracked files)', color='white') + print_info('and filtering by git tracked files', color='white') + changes = get_folders_diff_except_public( + Path(public_dir), + Path(old_dir), + Path(current_folder), + exclude_patterns=['.git'], + ) + # filter by tracked by git + git_tracked_files = get_tracked_files_list(Path(current_folder)) + changes = [f for f in changes if f in git_tracked_files] + + print_info('\nchanged_files:', color='white') + for change in changes: + print_info(f' ->> {change}', color='white') + + return changes + + def grade_on_ci( course_config: CourseConfig, course_schedule: CourseSchedule, @@ -280,23 +367,44 @@ def grade_on_ci( print_info(f'-> job_start_time {job_start_time}', color='grey') print_info(f'= using send_time {send_time}', color='grey') - # Get changed files via git + author_name = os.environ.get('CI_COMMIT_AUTHOR', None) + current_commit_sha = os.environ.get('CI_COMMIT_SHA', None) + prev_commit_sha = os.environ.get('CI_COMMIT_BEFORE_SHA', None) + print_info(f'CI_COMMIT_AUTHOR {author_name}', color='grey') + print_info(f'CI_COMMIT_SHA {current_commit_sha}', color='grey') + print_info(f'CI_COMMIT_BEFORE_SHA {prev_commit_sha}', color='grey') + + gitlab_job_token = os.environ.get('CI_JOB_TOKEN') or '' + + print_info('Loading changes...', color='orange') + # Get changes using real files difference try: - author_name = os.environ.get('CI_COMMIT_AUTHOR', None) - current_commit_sha = os.environ.get('CI_COMMIT_SHA', None) - prev_commit_sha = os.environ.get('CI_COMMIT_BEFORE_SHA', None) - - changes = _get_git_changes( - solution_root, - course_config.gitlab_url + '/' + course_config.public_repo, - author_name=author_name, - current_commit_sha=current_commit_sha, - prev_commit_sha=prev_commit_sha, + current_repo_gitlab_path = os.environ['CI_PROJECT_PATH'] + changes = _get_changes_using_real_folders( + course_config, + current_folder=solution_root, + old_hash=prev_commit_sha or course_config.default_branch, + current_repo_gitlab_path=current_repo_gitlab_path, + gitlab_token=gitlab_job_token, ) - except GitException as e: + except Exception as e: print_info('Ooops... Loading changes failed', color='red') print_info(e) - sys.exit(1) + + print_info('Trying with git diff instead\n') + # Get changed files via git + try: + changes = _get_git_changes( + solution_root, + course_config.gitlab_url + '/' + course_config.public_repo, + author_name=author_name, + current_commit_sha=current_commit_sha, + prev_commit_sha=prev_commit_sha, + ) + except GitException as e: + print_info('Ooops... Loading changes failed', color='red') + print_info(e) + sys.exit(1) # Process Changed files to Changed tasks tasks: list[Task] = [] diff --git a/checker/testers/cpp.py b/checker/testers/cpp.py index 67316ba..3a165f2 100644 --- a/checker/testers/cpp.py +++ b/checker/testers/cpp.py @@ -117,7 +117,7 @@ def _gen_build( # type: ignore[override] try: print_info('Running clang tidy...', color='orange') - files = [str(file) for file in task_dir.rglob('*.cpp')] + files = [str(file) for file in task_dir.rglob('*.cpp')] # type: ignore self._executor( ['clang-tidy', '-p', '.', *files], cwd=build_dir, diff --git a/checker/testers/python.py b/checker/testers/python.py index 1ce325b..0b533e9 100644 --- a/checker/testers/python.py +++ b/checker/testers/python.py @@ -342,6 +342,7 @@ def _run_tests( # type: ignore[override] # Check tests tests_err = None + tests_output = '' try: print_info('Running tests...', color='orange') output = self._executor( @@ -356,13 +357,11 @@ def _run_tests( # type: ignore[override] print_info(output, end='') print_info('OK', color='green') except ExecutionFailedError as e: - if not test_config.partially_scored: - # Reraise only if all tests should pass - tests_err = e - output = e.output + tests_err = e + tests_output = e.output or '' if normalize_output or test_config.partially_scored: - print_info(output, end='') + print_info(e.output, end='') e.output = '' output = '' @@ -374,7 +373,7 @@ def _run_tests( # type: ignore[override] if import_err is not None: raise RunFailedError('Import error', output=import_err.output) from import_err - if tests_err is not None: + if tests_err is not None and not test_config.partially_scored: # Reraise only if all tests should pass raise TestsFailedError('Public or private tests error', output=tests_err.output) from tests_err if styles_err is not None: @@ -384,7 +383,7 @@ def _run_tests( # type: ignore[override] raise StylecheckFailedError('Typing error', output=typing_err.output) from typing_err if test_config.partially_scored: - output = output or '' # for mypy only - return self._parse_summary_score(output) + tests_output = tests_output or '' # for mypy only + return self._parse_summary_score(tests_output) else: return 1. diff --git a/checker/utils/files.py b/checker/utils/files.py index b531be9..67c6afa 100644 --- a/checker/utils/files.py +++ b/checker/utils/files.py @@ -2,6 +2,7 @@ import re import shutil +import subprocess from pathlib import Path from .print import print_info @@ -140,3 +141,107 @@ def check_files_contains_regexp( raise AssertionError(f'File <{source_path}> contains one of <{regexps}>') return True return False + + +def get_folders_diff( + old_folder: Path, + new_folder: Path, + skip_binary: bool = True, + exclude_patterns: list[str] | None = None, +) -> list[str]: + """ + Return diff files between 2 folders + @param old_folder: Old folder + @param new_folder: New folder with some changes files, based on old folder + @param skip_binary: Skip binary files + @param exclude_patterns: Exclude files that match pattern + @return: list of changed files as strings + """ + # diff docs https://www.gnu.org/software/diffutils/manual/html_node/diff-Options.html + # -N/--new-file - If one file is missing, treat it as present but empty + # -w/--ignore-all-space - ignore all spaces and tabs e.g. if ( a == b) is equal to if(a==b) + # -r/--recursive - recursively compare any subdirectories found + # -q/--brief - report only when files differ + # --strip-trailing-cr - strip trailing carriage return on input + # -x/--exclude [pattern] - exclude files that match pattern + + # TODO: check format options to work, or --left-column options + exclude_args = [f'--exclude={pattern}' for pattern in exclude_patterns] if exclude_patterns else [] + # exclude_args = [] + result = subprocess.run( + [ + 'diff', + '--brief', + '--recursive', + '--ignore-all-space', + '--new-file', + '--strip-trailing-cr', + *exclude_args, + old_folder.absolute(), + new_folder.absolute() + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + output = result.stdout.decode() + + # TODO: make it work with whitespace in filenames + + changed = [] + for line in output.split('\n'): + if line.startswith('Only in'): + assert False, 'Will be treated as change due to --new-file option' + elif line.startswith('Files'): + _, file1, _, file2, _ = line.split() + changed.append(Path(file2).relative_to(new_folder)) + elif line.startswith('Binary files'): + if skip_binary: + continue + _, _, file1, _, file2, _ = line.split() + changed.append(Path(file2).relative_to(new_folder)) + + return [str(i) for i in changed] + + +def get_folders_diff_except_public( + public_folder: Path, + old_folder: Path, + new_folder: Path, + skip_binary: bool = True, + exclude_patterns: list[str] | None = None, +) -> list[str]: + """ + Return diff files between 2 folders except files that are equal to public folder files + @param public_folder: Public folder + @param old_folder: Old folder + @param new_folder: New folder with some changes files, based on old folder + @param skip_binary: Skip binary files + @param exclude_patterns: Exclude files that match pattern + @return: list of changed files as strings + """ + + changed_files_old_new = get_folders_diff( + old_folder, + new_folder, + skip_binary=skip_binary, + exclude_patterns=exclude_patterns, + ) + changed_files_public_new = get_folders_diff( + public_folder, + new_folder, + skip_binary=skip_binary, + exclude_patterns=exclude_patterns, + ) + + # TODO: Remove logging + print_info('\nchanged_files_old_new:', color='grey') + for i in changed_files_old_new: + print_info(f' {i}', color='grey') + print_info('\nchanged_files_public_new:', color='grey') + for i in changed_files_public_new: + print_info(f' {i}', color='grey') + + return [ + str(i) + for i in set(changed_files_old_new) & set(changed_files_public_new) + ] diff --git a/checker/utils/git.py b/checker/utils/git.py index 8b4cede..abca271 100644 --- a/checker/utils/git.py +++ b/checker/utils/git.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import shutil import subprocess from pathlib import Path @@ -8,6 +10,21 @@ DEFAULT_BRANCH = 'main' +def get_tracked_files_list( + repo_dir: Path, +) -> list[str]: + r = subprocess.run( + 'git ls-files', + encoding='utf-8', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + check=True, + cwd=repo_dir, + ) + return r.stdout.splitlines() + + def setup_repo_in_dir( repo_dir: Path, remote_repo_url: str, diff --git a/tests/course/test_config.py b/tests/course/test_config.py index 82cf9f9..7e7a20f 100644 --- a/tests/course/test_config.py +++ b/tests/course/test_config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/examples/test_course_configs.py b/tests/examples/test_course_configs.py index f5b07e5..3e44e46 100644 --- a/tests/examples/test_course_configs.py +++ b/tests/examples/test_course_configs.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from checker.course.config import CourseConfig diff --git a/tests/executors/test_sandbox.py b/tests/executors/test_sandbox.py index 7c2c9ee..1191461 100644 --- a/tests/executors/test_sandbox.py +++ b/tests/executors/test_sandbox.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/testers/test_cpp.py b/tests/testers/test_cpp.py index 5c90cd9..6b3fef0 100644 --- a/tests/testers/test_cpp.py +++ b/tests/testers/test_cpp.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import stat from pathlib import Path diff --git a/tests/testers/test_tester.py b/tests/testers/test_tester.py index 2d2d3b2..c3ca99d 100644 --- a/tests/testers/test_tester.py +++ b/tests/testers/test_tester.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect from dataclasses import dataclass from pathlib import Path diff --git a/tests/utils/test_files.py b/tests/utils/test_files.py index 8568eef..5487edf 100644 --- a/tests/utils/test_files.py +++ b/tests/utils/test_files.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from pathlib import Path import pytest @@ -10,6 +11,8 @@ check_folder_contains_regexp, copy_files, filename_match_patterns, + get_folders_diff, + get_folders_diff_except_public, ) @@ -157,3 +160,205 @@ def test_check_files_contains_regexp( f1.write('123') f2.write('321') assert check_files_contains_regexp(tmp_path, regexps, regexps_for_files) == contains + + +class TestFolderDiff: + @pytest.fixture(scope='function') + def public_folder(self, tmp_path: Path) -> Path: + public_folder = tmp_path / 'public' + public_folder.mkdir() + return public_folder + + @pytest.fixture(scope='function') + def old_folder(self, tmp_path: Path) -> Path: + old_folder = tmp_path / 'old' + old_folder.mkdir() + return old_folder + + @pytest.fixture(scope='function') + def new_folder(self, tmp_path: Path) -> Path: + new_folder = tmp_path / 'new' + new_folder.mkdir() + return new_folder + + @staticmethod + def fill_folder(folder: Path, files: list[str], content: str) -> None: + folder.mkdir(parents=True, exist_ok=True) + for file in files: + with open(folder / file, 'w') as f: + f.write(content) + + @staticmethod + def fill_folder_binary_files(folder: Path, files: list[str], content: bytes) -> None: + folder.mkdir(parents=True, exist_ok=True) + for file in files: + with open(folder / file, 'wb') as f: + f.write(content) + + def test_flat_folders(self, old_folder: Path, new_folder: Path) -> None: + # same files + for i in range(10): + self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + + # completely different files + different_files = ['a.py', 'b.cpp', 'c.go'] + self.fill_folder(old_folder, different_files, '1\n2\n3\n'*16) + self.fill_folder(new_folder, different_files, '4\n5\n6\n'*16) + + changed_files = get_folders_diff(old_folder, new_folder) + assert sorted(changed_files) == sorted(different_files) + + # def test_flat_folders_spaces_diff(self, old_folder: Path, new_folder: Path) -> None: + # # same files + # for i in range(10): + # self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + # self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + # + # # completely different files + # space_different_files = ['a.py', 'b.cpp', 'c.go'] + # self.fill_folder(old_folder, space_different_files, 'Here lyeth muche rychnesse in lytell space.-- John Heywood$') + # self.fill_folder(new_folder, space_different_files, ' He relyeth much erychnes seinly tells pace. --John Heywood ^M$') + # + # changed_files = get_folders_diff(old_folder, new_folder) + # assert len(changed_files) == 0 + + def test_flat_folders_only_same_files(self, old_folder: Path, new_folder: Path) -> None: + # same files + for i in range(10): + self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + + changed_files = get_folders_diff(old_folder, new_folder) + assert len(changed_files) == 0 + + def test_flat_folders_new_and_deleted_files(self, old_folder: Path, new_folder: Path) -> None: + # same files + for i in range(10): + self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + + # deleted files + deleted_files = ['to_be_deleted_a.py', 'to_be_deleted_b.cpp', 'to_be_deleted_c.go'] + self.fill_folder(old_folder, deleted_files, '1\n2\n3\n'*16) + # new files + new_files = ['new_file_a.py', 'new_file_.cpp', 'new_file_.go'] + self.fill_folder(new_folder, new_files, '1\n2\n3\n'*16) + + changed_files = get_folders_diff(old_folder, new_folder) + assert sorted(changed_files) == sorted(deleted_files + new_files) + + # def test_flat_folders_spaces_in_filename(self, old_folder: Path, new_folder: Path) -> None: + # # same files + # for i in range(10): + # self.fill_folder(old_folder, [f'{i} some {i}.py', f'{i} some {i}.cpp', f'{i} some {i}.go'], '1\n2\n3\n'*16) + # self.fill_folder(new_folder, [f'{i} some {i}.py', f'{i} some {i}.cpp', f'{i} some {i}.go'], '1\n2\n3\n'*16) + # + # # completely different files + # different_files = ['a some a.py', 'b some b.cpp', 'c some c.go'] + # self.fill_folder(old_folder, different_files, '1\n2\n3\n'*16) + # self.fill_folder(new_folder, different_files, '4\n5\n6\n'*16) + # + # changed_files = get_folders_diff(old_folder, new_folder) + # assert sorted(changed_files) == sorted(different_files) + + # TODO: make binary files detection to work on ubuntu + # def test_flat_folders_skip_binary_files(self, old_folder: Path, new_folder: Path) -> None: + # # same files + # for i in range(10): + # self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + # self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + # + # # completely different files + # different_files = ['a.py', 'b.cpp', 'c.go'] + # self.fill_folder_binary_files(old_folder, different_files, b'\x00'+os.urandom(64)+b'\x00') + # self.fill_folder_binary_files(new_folder, different_files, b'\x00'+os.urandom(64)+b'\x00') + # + # changed_files = get_folders_diff(old_folder, new_folder, skip_binary=False) + # assert sorted(changed_files) == sorted(different_files) + # + # changed_files = get_folders_diff(old_folder, new_folder) + # assert len(changed_files) == 0 + # changed_files = get_folders_diff(old_folder, new_folder, skip_binary=True) + # assert len(changed_files) == 0 + + def test_deep_structure(self, old_folder: Path, new_folder: Path) -> None: + # same files + for i in range(10): + self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + + # changed files in top folder + different_files = ['a.py', 'b.cpp', 'c.go'] + self.fill_folder(old_folder, different_files, '1\n2\n3\n'*16) + self.fill_folder(new_folder, different_files, '4\n3\n2\n'*16) + + # changed files in inner folders + inner_folder_different_files = ['o.py', 'p.cpp', 'q.go'] + self.fill_folder(old_folder / 'inner-folder', inner_folder_different_files, '1\n2\n3\n'*16) + self.fill_folder(new_folder / 'inner-folder', inner_folder_different_files, '4\n3\n2\n'*16) + + # new inner folder + new_inner_folder_files = ['t.py', 'r.cpp', 'n.go'] + self.fill_folder(new_folder / 'new-inner-folder', new_inner_folder_files, '1\n2\n3\n'*16) + + changed_files = get_folders_diff(old_folder, new_folder) + assert len(changed_files) == len(different_files + inner_folder_different_files + new_inner_folder_files) + assert all(file in changed_files for file in different_files) + assert all(f'inner-folder/{file}' in changed_files for file in inner_folder_different_files) + assert all(f'new-inner-folder/{file}' in changed_files for file in new_inner_folder_files) + + def test_deep_structure_skip_folders(self, old_folder: Path, new_folder: Path) -> None: + # same files + for i in range(10): + self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + + # changed files in inner folders + inner_folder_different_files = ['o.py', 'p.cpp', 'q.go'] + self.fill_folder(old_folder / 'inner-folder', inner_folder_different_files, '1\n2\n3\n'*16) + self.fill_folder(new_folder / 'inner-folder', inner_folder_different_files, '4\n3\n2\n'*16) + + # changed files in inner folders + skip_inner_folder_different_files = ['a.py', 'b.cpp', 'c.go'] + self.fill_folder(old_folder / 'skip-inner-folder', skip_inner_folder_different_files, '1\n2\n3\n'*16) + self.fill_folder(new_folder / 'skip-inner-folder', skip_inner_folder_different_files, '4\n3\n2\n'*16) + + # changed files in inner folders + git_folder_different_files = ['aa.py', 'bb.cpp', 'cc.go'] + self.fill_folder(old_folder / '.git', git_folder_different_files, '1\n2\n3\n'*16) + self.fill_folder(new_folder / '.git', git_folder_different_files, '4\n3\n2\n'*16) + + changed_files = get_folders_diff(old_folder, new_folder, exclude_patterns=['.git', 'skip-inner-folder']) + assert sorted(changed_files) == sorted([f'inner-folder/{i}' for i in inner_folder_different_files]) + + def test_flat_public_folder_filtering(self, public_folder: Path, old_folder: Path, new_folder: Path) -> None: + # same files + for i in range(10): + self.fill_folder(old_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(new_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + self.fill_folder(public_folder, [f'{i}.py', f'{i}.cpp', f'{i}.go'], '1\n2\n3\n'*16) + + # new files in public not in old/new + new_files_in_public = ['new_in_public_a.py', 'new_in_public_b.cpp', 'new_in_public_c.go'] + self.fill_folder(public_folder, new_files_in_public, '1\n2\n3\n'*16) + + # totally new files in new + new_files_in_new = ['new_in_new_a.py', 'new_in_new_b.cpp', 'new_in_new_c.go'] + self.fill_folder(new_folder, new_files_in_new, '1\n2\n3\n'*16) + + # new in public and transfer in new + new_files_in_public_and_new = ['new_in_public_and_new_a.py', 'new_in_public_and_new_b.cpp', 'new_in_public_and_new_c.go'] + self.fill_folder(public_folder, new_files_in_public_and_new, '1\n2\n3\n'*16) + self.fill_folder(new_folder, new_files_in_public_and_new, '1\n2\n3\n'*16) + + # new in public than changes in new + new_files_in_public_and_new_changed = ['new_in_public_and_new_changed_a.py', 'new_in_public_and_new_changed_b.cpp', 'new_in_public_and_new_changed_c.go'] + self.fill_folder(public_folder, new_files_in_public_and_new_changed, '1\n2\n3\n'*16) + self.fill_folder(new_folder, new_files_in_public_and_new_changed, '4\n3\n2\n'*16) + + changed_files = get_folders_diff_except_public(public_folder, old_folder, new_folder) + print('\nchanged_files') + for i in changed_files: + print('-', i) + assert sorted(changed_files) == sorted(new_files_in_new + new_files_in_public_and_new_changed) diff --git a/tests/utils/test_git.py b/tests/utils/test_git.py new file mode 100644 index 0000000..ca588d5 --- /dev/null +++ b/tests/utils/test_git.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from pathlib import Path + +from checker.utils import get_tracked_files_list + + +ROOT_DIR = Path(__file__).parent.parent.parent + + +class TestGitStats: + + def test_get_tracked_files_list(self) -> None: + current_file = Path(__file__).absolute().relative_to(ROOT_DIR) + main_file = (ROOT_DIR / 'checker' / '__main__.py').absolute().relative_to(ROOT_DIR) + git_tracked_files = get_tracked_files_list(ROOT_DIR) + + assert len(git_tracked_files) > 0 + assert str(current_file) in git_tracked_files + assert str(main_file) in git_tracked_files diff --git a/tests/utils/test_manytask.py b/tests/utils/test_manytask.py index 322d604..4cfcf4d 100644 --- a/tests/utils/test_manytask.py +++ b/tests/utils/test_manytask.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import datetime import pytest diff --git a/tests/utils/test_print.py b/tests/utils/test_print.py index c987dc4..5dd8435 100644 --- a/tests/utils/test_print.py +++ b/tests/utils/test_print.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from checker.utils import print_info, print_task_info