Permalink
Browse files

Merged in refactoring branch.

------------------------------------------------------------
Use --include-merged or -n0 to see merged revisions.
  • Loading branch information...
1 parent 6f3e756 commit fd1739b0fd12c97d7d2d85b6a5d0878da80e6750 @mkwiatkowski committed with Jan 14, 2010
View
@@ -1,4 +1,5 @@
include Changelog
+include test/factories.py
include test/helper.py
include test/__init__.py
include test/data/*.py
View
0 lib2to3/pgen2/token.py 100755 → 100644
No changes.
@@ -0,0 +1,92 @@
+import os
+
+from pythoscope.logger import log
+from pythoscope.util import string2filename, load_pickle_from
+
+
+class CodeTreeNotFound(Exception):
+ def __init__(self, module_subpath):
+ Exception.__init__(self, "Couldn't find code tree for module %r." % module_subpath)
+ self.module_subpath = module_subpath
+
+class CodeTreesManager(object):
+ def __init__(self, code_trees_path):
+ raise NotImplementedError
+
+ # :: (CodeTree, str) -> None
+ def remember_code_tree(self, code_tree, module_subpath):
+ raise NotImplementedError
+
+ # :: str -> CodeTree
+ def recall_code_tree(self, module_subpath):
+ """Return code tree corresponding to a module located under given subpath.
+
+ May raise CodeTreeNotFound exception.
+ """
+ raise NotImplementedError
+
+ # :: str -> None
+ def forget_code_tree(self, module_subpath):
+ """Get rid of the CodeTree for a module located under given subpath.
+ Do nothing if the module doesn't exist.
+ """
+ raise NotImplementedError
+
+ def clear_cache(self):
+ pass
+
+class FilesystemCodeTreesManager(CodeTreesManager):
+ """Manager of CodeTree instances that keeps at most one CodeTree instance
+ in a memory, storing the rest in files.
+ """
+ def __init__(self, code_trees_path):
+ self.code_trees_path = code_trees_path
+ self._cached_code_tree = None
+
+ def remember_code_tree(self, code_tree, module_subpath):
+ log.debug("Saving code tree for module %r to a file and caching..." % \
+ module_subpath)
+ code_tree.save(self._code_tree_path(module_subpath))
+ self._cache(code_tree, module_subpath)
+
+ def recall_code_tree(self, module_subpath):
+ if self._is_cached(module_subpath):
+ return self._cached_code_tree[1]
+ try:
+ log.debug("Loading code tree for module %r from a file and caching..." % \
+ module_subpath)
+ code_tree = load_pickle_from(self._code_tree_path(module_subpath))
+ self._cache(code_tree, module_subpath)
+ return code_tree
+ except IOError:
+ raise CodeTreeNotFound(module_subpath)
+
+ def forget_code_tree(self, module_subpath):
+ try:
+ os.remove(self._code_tree_path(module_subpath))
+ except OSError:
+ pass
+ self._remove_from_cache(module_subpath)
+
+ def clear_cache(self):
+ if self._cached_code_tree:
+ old_module_subpath, old_code_tree = self._cached_code_tree
+ log.debug("Code tree for module %r gets out of cache, "\
+ "saving to a file..." % old_module_subpath)
+ old_code_tree.save(self._code_tree_path(old_module_subpath))
+ self._cached_code_tree = None
+
+ def _cache(self, code_tree, module_subpath):
+ self.clear_cache()
+ self._cached_code_tree = (module_subpath, code_tree)
+
+ def _is_cached(self, module_subpath):
+ return self._cached_code_tree and self._cached_code_tree[0] == module_subpath
+
+ def _remove_from_cache(self, module_subpath):
+ if self._is_cached(module_subpath):
+ self._cached_code_tree = None
+
+ def _code_tree_path(self, module_subpath):
+ code_tree_filename = string2filename(module_subpath) + '.pickle'
+ return os.path.join(self.code_trees_path, code_tree_filename)
@@ -7,18 +7,101 @@
from pythoscope.logger import log
from pythoscope.util import max_by_not_zero, module_path_to_name
+from pythoscope.store import Module, TestClass, code_of, CodeTree
+from pythoscope.astvisitor import find_last_leaf, get_starting_whitespace, \
+ is_node_of_type, remove_trailing_whitespace
+from pythoscope.astbuilder import EmptyCode, Newline, create_import, \
+ insert_after, insert_before
def add_test_case_to_project(project, test_class, main_snippet=None, force=False):
existing_test_class = find_test_class_by_name(project, test_class.name)
if not existing_test_class:
- place = find_place_for_test_class(project, test_class)
- log.info("Adding generated %s to %s." % (test_class.name, place.subpath))
- place.add_test_case(test_class)
- place.ensure_main_snippet(main_snippet)
+ module = find_module_for_test_class(project, test_class)
+ log.info("Adding generated %s to %s." % (test_class.name, module.subpath))
+ ensure_imports(module, test_class.imports)
+ add_test_case(module, test_class)
+ ensure_main_snippet(module, main_snippet, force)
else:
+ ensure_imports(existing_test_class, test_class.imports)
merge_test_classes(existing_test_class, test_class, force)
- existing_test_class.parent.ensure_main_snippet(main_snippet)
+ ensure_main_snippet(existing_test_class.parent, main_snippet, force)
+
+def add_test_case_without_append(test_suite, test_case):
+ test_suite.add_test_case_without_append(test_case)
+
+def add_test_case(test_suite, test_case):
+ if isinstance(test_suite, Module):
+ # If the main_snippet exists we have to put the new test case
+ # before it. If it doesn't we put the test case at the end.
+ main_snippet = code_of(test_suite, 'main_snippet')
+ if main_snippet:
+ insert_before(main_snippet, test_case.code)
+ else:
+ code_of(test_suite).append_child(test_case.code)
+ elif isinstance(test_suite, TestClass):
+ # Append to the right node, so that indentation level of the
+ # new method is good.
+ if code_of(test_suite).children and is_node_of_type(code_of(test_suite).children[-1], 'suite'):
+ remove_trailing_whitespace(test_case.code)
+ suite = code_of(test_suite).children[-1]
+ # Prefix the definition with the right amount of whitespace.
+ node = find_last_leaf(suite.children[-2])
+ ident = get_starting_whitespace(suite)
+ # There's no need to have extra newlines.
+ if node.prefix.endswith("\n"):
+ node.prefix += ident.lstrip("\n")
+ else:
+ node.prefix += ident
+ # Insert before the class contents dedent.
+ suite.insert_child(-1, test_case.code)
+ else:
+ code_of(test_suite).append_child(test_case.code)
+ else:
+ raise TypeError("Tried to add a test case to %r." % test_suite)
+ add_test_case_without_append(test_suite, test_case)
+ test_suite.mark_as_changed()
+
+def ensure_main_snippet(module, main_snippet, force=False):
+ """Make sure the main_snippet is present. Won't overwrite the snippet
+ unless force flag is set.
+ """
+ if not main_snippet:
+ return
+ current_main_snippet = code_of(module, 'main_snippet')
+
+ if not current_main_snippet:
+ code_of(module).append_child(main_snippet)
+ module.store_reference('main_snippet', main_snippet)
+ module.mark_as_changed()
+ elif force:
+ current_main_snippet.replace(main_snippet)
+ module.store_reference('main_snippet', main_snippet)
+ module.mark_as_changed()
+
+def ensure_imports(test_suite, imports):
+ if isinstance(test_suite, TestClass):
+ module = test_suite.parent
+ elif isinstance(test_suite, Module):
+ module = test_suite
+ else:
+ raise TypeError("Tried to ensure imports on %r." % test_suite)
+ for imp in imports:
+ if not module.contains_import(imp):
+ insert_after_other_imports(module, create_import(imp))
+ module.mark_as_changed()
+ test_suite.ensure_imports(imports)
+
+def insert_after_other_imports(module, code):
+ last_import = code_of(module, 'last_import')
+ if last_import:
+ insert_after(last_import, code)
+ else:
+ # Add an extra newline separating imports from the code.
+ code_of(module).insert_child(0, Newline())
+ code_of(module).insert_child(0, code)
+ # Just inserted import becomes the last one.
+ module.store_reference('last_import', code)
def find_test_class_by_name(project, name):
for tcase in project.iter_test_cases():
@@ -33,17 +116,34 @@ def merge_test_classes(test_class, other_test_class, force):
if not existing_test_method:
log.info("Adding generated %s to %s in %s." % \
(method.name, test_class.name, test_class.parent.subpath))
- test_class.add_test_case(method)
+ add_test_case(test_class, method)
elif force:
log.info("Replacing %s.%s from %s with generated version." % \
(test_class.name, existing_test_method.name, test_class.parent.subpath))
- test_class.replace_test_case(existing_test_method, method)
+ replace_test_case(test_class, existing_test_method, method)
else:
log.info("Test case %s.%s already exists in %s, skipping." % \
(test_class.name, existing_test_method.name, test_class.parent.subpath))
- test_class.ensure_imports(other_test_class.imports)
-def find_place_for_test_class(project, test_class):
+def replace_test_case(test_suite, old_test_case, new_test_case):
+ """Replace one test case object with another.
+
+ As a side effect, AST of the new test case will replace part of the AST
+ in the old test case parent.
+
+ `Code` attribute of the new test case object will be removed.
+ """
+ # The easiest way to get the new code inside the AST is to call
+ # replace() on the old test case code.
+ # It is destructive, but since we're discarding the old test case
+ # anyway, it doesn't matter.
+ code_of(old_test_case).replace(new_test_case.code)
+
+ test_suite.remove_test_case(old_test_case)
+ add_test_case_without_append(test_suite, new_test_case)
+ test_suite.mark_as_changed()
+
+def find_module_for_test_class(project, test_class):
"""Find the best place for the new test case to be added. If there is
no such place in existing test modules, a new one will be created.
"""
@@ -89,7 +189,7 @@ def create_test_module(project, test_case):
"""Create a new test module for a given test case.
"""
test_name = test_module_name_for_test_case(test_case)
- return project.create_test_module_from_name(test_name)
+ return project.create_test_module_from_name(test_name, code=EmptyCode())
def module_path_to_test_path(module):
"""Convert a module locator to a proper test filename.
View
@@ -0,0 +1,49 @@
+import os
+import time
+
+from pythoscope.util import ensure_directory, get_last_modification_time, \
+ module_path_to_name, write_content_to_file
+
+
+class Localizable(object):
+ """An object which has a corresponding file belonging to some Project.
+
+ Each Localizable has a 'path' attribute and an information when it was
+ created, to be in sync with its file system counterpart. Path is always
+ relative to the project this localizable belongs to.
+ """
+ def __init__(self, project, subpath, created=None):
+ self.project = project
+ self.subpath = subpath
+ if created is None:
+ created = time.time()
+ self.created = created
+
+ def _get_locator(self):
+ return module_path_to_name(self.subpath, newsep=".")
+ locator = property(_get_locator)
+
+ def is_out_of_sync(self):
+ """Is the object out of sync with its file.
+ """
+ return get_last_modification_time(self.get_path()) > self.created
+
+ def is_up_to_date(self):
+ return not self.is_out_of_sync()
+
+ def get_path(self):
+ """Return the full path to the file.
+ """
+ return os.path.join(self.project.path, self.subpath)
+
+ def write(self, new_content):
+ """Overwrite the file with new contents and update its created time.
+
+ Creates the containing directories if needed.
+ """
+ ensure_directory(os.path.dirname(self.get_path()))
+ write_content_to_file(new_content, self.get_path())
+ self.created = time.time()
+
+ def exists(self):
+ return os.path.isfile(self.get_path())
Oops, something went wrong.

0 comments on commit fd1739b

Please sign in to comment.