Skip to content

Commit

Permalink
add multiple tests
Browse files Browse the repository at this point in the history
  • Loading branch information
VL-CZ committed Oct 12, 2021
1 parent 26fdfe2 commit 00afa87
Show file tree
Hide file tree
Showing 51 changed files with 13,696 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/code_size_counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, directory, file_extensions, print_logs, excluded_items):
:param directory: the directory where to search files
:param file_extensions: extension of the files that we're searching
:param print_logs: should the program print its progress? (e.g. 'file XXX processed)
:param excluded_items: directories & files to exclude
:param excluded_items: absolute path to directories & files to exclude
"""
self._directory = directory
self._file_extensions = file_extensions
Expand Down
36 changes: 36 additions & 0 deletions tests/complex-test-dir/.github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python application

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with unittest module
run: |
python -m unittest discover tests -v
3 changes: 3 additions & 0 deletions tests/complex-test-dir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# source-code-size-counter
This repository contains a simple tool used for calculating total size (both kB and lines of code)
of program's code.
58 changes: 58 additions & 0 deletions tests/complex-test-dir/code_size_counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import os
from argparse import ArgumentParser

from src.code_size_counter import CodeSizeCounter


def format_to_kilobytes(total_bytes):
"""
convert the given number of bytes to kilobytes and round to 2 decimal digits
:param total_bytes: number of bytes to convert and format
:return: converted kilobytes rounded to 2 decimal digits
"""
kilobytes = total_bytes / 1024
return round(kilobytes, 2)


def format_extensions(file_extensions):
"""
format set of file extensions for printing
:param file_extensions: set of file extensions
"""
dot_prefixed_extensions = map(lambda e: f'.{e}', file_extensions)
return ", ".join(dot_prefixed_extensions)


def config_args():
"""
configure the command line arguments of the program
:return: parsed script arguments
"""
parser = ArgumentParser(description='Calculate the total size (both kB and lines of code) of program\'s code.')
parser.add_argument('-d', '--directory', type=str, required=True)
parser.add_argument('-e', '--extension', nargs='+', required=True, default=[])
parser.add_argument('-l', '--log', default=False, action='store_true')
parser.add_argument('-x', '--exclude', nargs='+', default=[])
return parser.parse_args()


def main():
args = config_args()
file_extensions = tuple(args.extension)
excluded_items = tuple(map(lambda ex: os.path.join(args.directory, ex), args.exclude))

code_size_counter = CodeSizeCounter(args.directory, file_extensions, args.log, excluded_items)
code_size = code_size_counter.calculate_size()

print('=' * 60)
print(f'Total {format_extensions(file_extensions)} files: {code_size.total_files}')
print(f'Total size of {format_extensions(file_extensions)} files: {format_to_kilobytes(code_size.total_size)} kB')
print(f'Total lines of code: {code_size.total_lines}')
print('=' * 60)


if __name__ == '__main__':
main()
Empty file.
71 changes: 71 additions & 0 deletions tests/complex-test-dir/src/code_size_counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os

from src.file_tools import FileSetSize, FileManager, get_path_with_slashes


class CodeSizeCounter:
"""
class responsible for computing the source code size in the given directory
"""

def __init__(self, directory, file_extensions, print_logs, excluded_items):
"""
:param directory: the directory where to search files
:param file_extensions: extension of the files that we're searching
:param print_logs: should the program print its progress? (e.g. 'file XXX processed)
:param excluded_items: absolute path to directories & files to exclude
"""
self._directory = directory
self._file_extensions = file_extensions
self._print_logs = print_logs
self._excluded_items = excluded_items

def calculate_size(self):
"""
count lines, size (in bytes) and number of files in the directory with the selected file extension
"""
return self._calculate_size(self._directory)

def _calculate_size(self, directory):
"""
count lines, size (in bytes) and number of files in the directory with the selected file extension
:param directory: directory where to search files
"""

# if it's in excluded files/directories, return
if self._is_excluded(directory):
return FileSetSize.empty()

items = os.listdir(directory)
files = [f for f in items if os.path.isfile(os.path.join(directory, f))]
directories = [d for d in items if os.path.isdir(os.path.join(directory, d))]

file_set_size = FileSetSize.empty()

for sub_dir in directories: # add the size of all subdirs
directory_path = os.path.join(directory, sub_dir)
directory_size = self._calculate_size(directory_path)
file_set_size.add(directory_size)

for file in files: # add the size of all files
file_path = os.path.join(directory, file)
file_manager = FileManager(file_path)
if (not file_manager.has_one_of_extensions(self._file_extensions)) or self._is_excluded(file_path):
continue

file_size = FileSetSize(file_manager.get_size(), file_manager.get_lines_count(), 1)
file_set_size.add(file_size)

if self._print_logs:
print(f'{get_path_with_slashes(file_path)} processed')

return file_set_size

def _is_excluded(self, path):
"""
check if the directory/file is excluded from the code size calculation
:param path: path of the directory/file to check
"""
return any(os.path.samefile(path, ex) for ex in self._excluded_items)
87 changes: 87 additions & 0 deletions tests/complex-test-dir/src/file_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os


class FileSetSize:
"""
class representing
- total size
- total lines count
- number of files
of a set of files
"""

def __init__(self, total_size, total_lines, total_files):
"""
:param total_size: total size of all files in the set (in bytes)
:param total_lines: total lines of code of all files in the set
:param total_files: number of files in the set
"""
self.total_size = total_size
self.total_lines = total_lines
self.total_files = total_files

def add(self, file_set_size):
"""
add file_set_info to the current object
e.g. sum the corresponding fields
:param file_set_size:
"""
self.total_files += file_set_size.total_files
self.total_lines += file_set_size.total_lines
self.total_size += file_set_size.total_size

@staticmethod
def empty():
"""
create empty instance of class FileSetSizeInfo
:return:
"""
return FileSetSize(0, 0, 0)


class FileManager:
"""
Helper class for file management
"""

def __init__(self, file_path):
self.file_path = file_path

def get_size(self):
"""
get size of the file in bytes
"""
return os.path.getsize(self.file_path)

def get_lines_count(self):
"""
get number of lines in the file
"""
with open(self.file_path, 'r') as file:
return sum(1 for _ in file)

def has_one_of_extensions(self, extensions):
"""
check if the file has one of the extensions
:param extensions: collection of expected extensions
"""
return any(self._has_extension(e) for e in extensions)

def _has_extension(self, extension):
"""
check if the file has given file extension (e.g. '.py')
:param extension: given file extension (e.g. '.py')
"""
return self.file_path.endswith(f'.{extension}')


def get_path_with_slashes(path):
"""
get path using forward slashes (e.g. replace back-slash by forward slash on Windows)
"""
return path.replace('\\', '/')
Empty file.
2 changes: 2 additions & 0 deletions tests/complex-test-dir/src/module1/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
if __name__ == '__main__':
print('Hello world')
Empty file.
2 changes: 2 additions & 0 deletions tests/complex-test-dir/src/module1/module1_1/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
if __name__ == '__main__':
print('Hello world')
Empty file.
2 changes: 2 additions & 0 deletions tests/complex-test-dir/src/module2/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
if __name__ == '__main__':
print('Hello world')
Empty file.
50 changes: 50 additions & 0 deletions tests/complex-test-dir/tests/test_code_size_counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
import unittest
from pathlib import Path

from src.code_size_counter import CodeSizeCounter


class TestCodeSizeCounter(unittest.TestCase):
"""
class containing tests for CodeSizeCounter class
"""

def test_calculate_size_simple(self):
code_size_counter = CodeSizeCounter(os.path.join(_get_tests_dir(), 'simple-test-dir'), ('txt',), False, ())
code_size = code_size_counter.calculate_size()

self.assertEqual(3, code_size.total_files)
self.assertEqual(2374, code_size.total_size)
self.assertEqual(35, code_size.total_lines)

def test_calculate_size_complex(self):
pass

def test_calculate_size_exclude_files(self):
excluded_directories = map(lambda ex: os.path.join(_get_tests_dir(), 'exclude-test-dir', ex),
[os.path.join('dir', 'dir-ex2'), 'dir-ex'])

code_size_counter = CodeSizeCounter(os.path.join(_get_tests_dir(), 'exclude-test-dir'), ('py',), False,
tuple(excluded_directories))
code_size = code_size_counter.calculate_size()

self.assertEqual(2, code_size.total_files)
self.assertEqual(104, code_size.total_size)
self.assertEqual(4, code_size.total_lines)

def test_calculate_size_no_matching_files(self):
pass


def _get_tests_dir():
"""
get path to /tests directory
:return: path to /tests directory
"""
return Path(__file__).parent.absolute()


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 00afa87

Please sign in to comment.