diff --git a/codemetrics/cloc.py b/codemetrics/cloc.py index 834b765..12c222d 100644 --- a/codemetrics/cloc.py +++ b/codemetrics/cloc.py @@ -9,7 +9,7 @@ import pandas as pd -from . import internals +from . import internals, scm __all__ = ["get_cloc"] @@ -24,7 +24,7 @@ class ClocEntry: def get_cloc( - path: str = ".", cloc_program: str = "cloc", cwd: pl.Path = None + project: scm.Project, path: str = ".", cloc_program: str = "cloc" ) -> pd.DataFrame: """Retrieve lines of code (LOC) using cloc.pl @@ -40,11 +40,11 @@ def get_cloc( comment and code counts. """ - internals.check_run_in_root(path, cwd) + internals.check_run_in_root(path, project.cwd) cmdline = [cloc_program, "--csv", "--by-file", path] cloc_entries = [] try: - output = internals.run(cmdline, cwd=cwd).split("\n") + output = internals.run(cmdline, cwd=project.cwd).split("\n") except FileNotFoundError as err: msg = ( f"{err}. Is {cloc_program} available? Please pass " diff --git a/tests/test_cloc.py b/tests/test_cloc.py index 2247310..4f785f5 100644 --- a/tests/test_cloc.py +++ b/tests/test_cloc.py @@ -4,6 +4,7 @@ """Tests for loc (lines of code) module.""" import io +import pathlib as pl import textwrap import unittest from unittest import mock @@ -11,7 +12,8 @@ import pandas as pd import codemetrics.cloc as loc -from tests.utils import add_data_frame_equality_func + +from . import utils class SimpleDirectory(unittest.TestCase): @@ -19,7 +21,7 @@ class SimpleDirectory(unittest.TestCase): def setUp(self): """Mocks the internal run command.""" - add_data_frame_equality_func(self) + utils.add_data_frame_equality_func(self) self.run_output = textwrap.dedent( """\ language,filename,blank,comment,code,"http://cloc.sourceforge.net" @@ -43,8 +45,8 @@ def tearDown(self): def test_cloc_reads_files(self): """cloc is called and reads the output csv file.""" - actual = loc.get_cloc() - self.run_.assert_called_with("cloc --csv --by-file .".split(), cwd=None) + actual = loc.get_cloc(utils.FakeProject()) + self.run_.assert_called_with("cloc --csv --by-file .".split(), cwd=pl.Path(".")) expected = pd.read_csv( io.StringIO( textwrap.dedent( @@ -65,7 +67,7 @@ def test_cloc_not_found(self): """Clean error message when cloc is not found in the path.""" self.run_.side_effect = [FileNotFoundError] with self.assertRaises(FileNotFoundError) as context: - _ = loc.get_cloc() + _ = loc.get_cloc(utils.FakeProject()) self.assertIn("cloc", str(context.exception)) @@ -76,7 +78,7 @@ class TestClocCall(unittest.TestCase): def test_cloc_fails_if_not_in_root(self, path_glob): """Make sure that command line call checks it is run from the root.""" with self.assertRaises(ValueError) as context: - loc.get_cloc() + loc.get_cloc(utils.FakeProject()) path_glob.assert_called_with(pattern=".svn") self.assertIn("git or svn root", str(context.exception)) @@ -84,5 +86,7 @@ def test_cloc_fails_if_not_in_root(self, path_glob): @mock.patch("codemetrics.internals.run", autospec=True) def test_cloc_called_with_path(self, run, _): """Make sure the path is passed as argument to cloc when passed to the function.""" - loc.get_cloc(path="some-path") - run.assert_called_with(["cloc", "--csv", "--by-file", "some-path"], cwd=None) + loc.get_cloc(utils.FakeProject(), path="some-path") + run.assert_called_with( + ["cloc", "--csv", "--by-file", "some-path"], cwd=pl.Path(".") + ) diff --git a/tests/test_core.py b/tests/test_core.py index d8a85ea..c013882 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,7 +5,6 @@ import datetime as dt import io -import pathlib as pl import textwrap import unittest import unittest.mock as mock @@ -13,7 +12,6 @@ import lizard as lz import numpy as np import pandas as pd -import tqdm import codemetrics as cm import codemetrics.scm as scm @@ -403,23 +401,6 @@ def other(): """ ) - class FakeProject(scm.Project): - """Fake project with pre-determined values for the download return values.""" - - def download(self, data: pd.DataFrame) -> scm.DownloadResult: - pass - - def get_log( - self, - path: pl.Path = pl.Path("."), - after: dt.datetime = None, - before: dt.datetime = None, - progress_bar: tqdm.tqdm = None, - # FIXME: Why do we need path _and_ relative_url - relative_url: str = None, - ) -> pd.DataFrame: - pass - def setUp(self): super().setUp() self.log = pd.read_csv( @@ -435,9 +416,9 @@ def setUp(self): def get_complexity(self): """Factor retrieval of complexity""" - project = self.FakeProject() + project = utils.FakeProject() with mock.patch.object( - self.FakeProject, + utils.FakeProject, "download", autospec=True, side_effect=[ @@ -470,9 +451,9 @@ def test_handles_no_function(self): + cm.core._lizard_fields + "file_tokens file_nloc".split() ) - project = self.FakeProject() + project = utils.FakeProject() with mock.patch.object( - self.FakeProject, + utils.FakeProject, "download", autospec=True, return_value=scm.DownloadResult(1, "f.py", ""), @@ -490,14 +471,14 @@ def test_handles_no_function(self): def test_analysis_empty_input_return_empty_output(self, _): """Empty input returns and empty dataframe.""" self.log = self.log.iloc[:0] - actual = cm.get_complexity(self.log, self.FakeProject()) + actual = cm.get_complexity(self.log, utils.FakeProject()) self.assertTrue(actual.empty) def test_use_default_download(self): """When the context.downlad_funcc is defined, use it.""" - project = self.FakeProject() + project = utils.FakeProject() with mock.patch.object( - self.FakeProject, "download", return_value=scm.DownloadResult(1, "/", "") + utils.FakeProject, "download", return_value=scm.DownloadResult(1, "/", "") ) as download: _ = cm.get_complexity(self.log, project) download.assert_called_with(self.log) diff --git a/tests/utils.py b/tests/utils.py index 42ab1e3..07b7bdd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -5,11 +5,14 @@ """Test utility functions and wrappers.""" +import datetime as dt import io +import pathlib as pl import unittest import pandas as pd import pandas.testing as pdt +import tqdm import codemetrics.scm as scm @@ -86,3 +89,21 @@ def csvlog_to_dataframe(csv_log: str) -> pd.DataFrame: # Reorder columns. df = df[scm.LogEntry.__slots__].pipe(scm.normalize_log) return df + + +class FakeProject(scm.Project): + """Fake project with pre-determined values for the download return values.""" + + def download(self, data: pd.DataFrame) -> scm.DownloadResult: + pass + + def get_log( + self, + path: pl.Path = pl.Path("."), + after: dt.datetime = None, + before: dt.datetime = None, + progress_bar: tqdm.tqdm = None, + # FIXME: Why do we need path _and_ relative_url + relative_url: str = None, + ) -> pd.DataFrame: + pass