[#138] Added a very preliminary version of the prioritization dialog. A big part of this commit is about refactoring the tests and introducing a TestApp for dupeGuru (in core.tests.base).

This commit is contained in:
Virgil Dupras 2011-09-07 15:46:41 -04:00
parent ff228035a3
commit 518228a368
9 changed files with 225 additions and 64 deletions

View File

@ -223,7 +223,7 @@ def getmatches_by_contents(files, sizeattr='size', partial=False, j=job.nulljob)
j.add_progress(desc=tr("%d matches found") % len(result))
return result
class Group(object):
class Group:
#---Override
def __init__(self):
self._clear()

View File

@ -0,0 +1,30 @@
# Created By: Virgil Dupras
# Created On: 2011-09-06
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from hscommon.gui.selectable_list import SelectableList
from ..prioritize import KindCategory
class CriterionCategoryList(SelectableList):
def __init__(self, dialog):
self.dialog = dialog
SelectableList.__init__(self, [c.NAME for c in dialog.categories])
def _update_selection(self):
self.dialog.select_category(self.dialog.categories[self.selected_index])
class PrioritizeDialog:
def __init__(self, view, app):
self.categories = [KindCategory(app.results)]
self.category_list = CriterionCategoryList(self)
self.criteria_list = []
def select_category(self, category):
criteria = category.criteria_list()
self.criteria_list = [c.value for c in criteria]

41
core/prioritize.py Normal file
View File

@ -0,0 +1,41 @@
# Created By: Virgil Dupras
# Created On: 2011/09/07
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from hscommon.util import dedupe, flatten
class CriterionCategory:
NAME = "Undefined"
def __init__(self, results):
self.results = results
#--- Virtual
def _extract_value(self, dupe):
raise NotImplementedError()
#--- Public
def criteria_list(self):
dupes = flatten(g[:] for g in self.results.groups)
values = dedupe(self._extract_value(d) for d in dupes)
return [Criterion(self, value) for value in values]
class Criterion:
def __init__(self, category, value):
self.category = category
self.value = value
@property
def display(self):
return "{} ({})".format(self.category, self.value)
class KindCategory(CriterionCategory):
NAME = "Kind"
def _extract_value(self, dupe):
return dupe.extension

View File

@ -16,36 +16,16 @@ from hscommon.path import Path
import hscommon.conflict
import hscommon.util
from hscommon.testutil import CallLogger, eq_, log_calls
from jobprogress.job import nulljob, Job, JobCancelled
from jobprogress.job import Job
from . import data
from .base import DupeGuru
from .results_test import GetTestGroups
from .. import app, fs, engine
from ..app import DupeGuru as DupeGuruBase
from ..gui.details_panel import DetailsPanel
from ..gui.directory_tree import DirectoryTree
from ..gui.result_table import ResultTable
from ..scanner import ScanType
class DupeGuru(DupeGuruBase):
JOB = nulljob
def __init__(self):
DupeGuruBase.__init__(self, data, '/tmp')
def _start_job(self, jobid, func, *args):
try:
func(self.JOB, *args)
except JobCancelled:
return
def _get_default(self, key_name):
return None
def _set_default(self, key_name, value):
pass
def add_fake_files_to_directories(directories, files):
directories.get_files = lambda j=None: iter(files)
directories._dirs.append('this is just so Scan() doesnt return 3')
@ -185,6 +165,7 @@ class TestCaseDupeGuru_clean_empty_dirs:
class TestCaseDupeGuruWithResults:
def pytest_funcarg__do_setup(self, request):
# XXX eventually, convert this to TestApp-based tests
self.app = DupeGuru()
self.objects,self.matches,self.groups = GetTestGroups()
self.app.results.groups = self.groups

103
core/tests/base.py Normal file
View File

@ -0,0 +1,103 @@
# Created By: Virgil Dupras
# Created On: 2011/09/07
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from hscommon.testutil import TestApp as TestAppBase, eq_, with_app
from hscommon.path import Path
from hscommon.util import get_file_ext
from jobprogress.job import nulljob, JobCancelled
from .. import engine
from ..engine import getwords
from ..app import DupeGuru as DupeGuruBase
from ..gui.details_panel import DetailsPanel
from ..gui.directory_tree import DirectoryTree
from ..gui.result_table import ResultTable
from ..gui.prioritize_dialog import PrioritizeDialog
from . import data
class DupeGuru(DupeGuruBase):
JOB = nulljob
def __init__(self):
DupeGuruBase.__init__(self, data, '/tmp')
def _start_job(self, jobid, func, *args):
try:
func(self.JOB, *args)
except JobCancelled:
return
def _get_default(self, key_name):
return None
def _set_default(self, key_name, value):
pass
class NamedObject:
def __init__(self, name="foobar", with_words=False, size=1, folder=None):
self.name = name
if folder is None:
folder = Path('basepath')
self._folder = folder
self.size = size
self.md5partial = name
self.md5 = name
if with_words:
self.words = getwords(name)
self.is_ref = False
def __bool__(self):
return False #Make sure that operations are made correctly when the bool value of files is false.
@property
def path(self):
return self._folder + self.name
@property
def folder_path(self):
return self.path[:-1]
@property
def extension(self):
return get_file_ext(self.name)
# Returns a group set that looks like that:
# "foo bar" (1)
# "bar bleh" (1024)
# "foo bleh" (1)
# "ibabtu" (1)
# "ibabtu" (1)
def GetTestGroups():
objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")]
objects[1].size = 1024
matches = engine.getmatches(objects) #we should have 5 matches
groups = engine.get_groups(matches) #We should have 2 groups
for g in groups:
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
groups.sort(key=len, reverse=True) # We want the group with 3 members to be first.
return (objects,matches,groups)
class TestApp(TestAppBase):
def __init__(self):
make_gui = self.make_gui
self.app = DupeGuru()
# XXX After hscommon.testutil.TestApp has had its default parent changed to seomthing
# customizable (with adjustments in moneyguru) we can get rid of 'parent='
make_gui('rtable', ResultTable, parent=self.app)
make_gui('dtree', DirectoryTree, parent=self.app)
make_gui('dpanel', DetailsPanel, parent=self.app)
make_gui('pdialog', PrioritizeDialog, parent=self.app)
for elem in [self.rtable, self.dtree, self.dpanel]:
elem.connect()
#--- Helpers
def select_pri_criterion(self, name):
# Select a main prioritize criterion by name instead of by index. Makes tests more
# maintainable.
index = self.pdialog.category_list.index(name)
self.pdialog.category_list.select(index)

1
core/tests/conftest.py Normal file
View File

@ -0,0 +1 @@
from hscommon.testutil import pytest_funcarg__app

View File

@ -12,19 +12,10 @@ from jobprogress import job
from hscommon.util import first
from hscommon.testutil import eq_, log_calls
from .base import NamedObject
from .. import engine
from ..engine import *
class NamedObject:
def __init__(self, name="foobar", with_words=False, size=1):
self.name = name
self.size = size
self.md5partial = name
self.md5 = name
if with_words:
self.words = getwords(name)
no = NamedObject
def get_match_triangle():

View File

@ -0,0 +1,43 @@
# Created By: Virgil Dupras
# Created On: 2011/09/07
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from itertools import combinations
from .base import TestApp, NamedObject, with_app, eq_
from ..engine import Group, Match
no = NamedObject
def app_with_dupes(dupes):
# Creates an app with specified dupes. dupes is a list of lists, each list in the list being
# a dupe group. We cheat a little bit by creating dupe groups manually instead of running a
# dupe scan, but it simplifies the test code quite a bit
app = TestApp()
groups = []
for dupelist in dupes:
g = Group()
for dupe1, dupe2 in combinations(dupelist, 2):
g.add_match(Match(dupe1, dupe2, 100))
groups.append(g)
app.app.results.groups = groups
app.app._results_changed()
return app
#---
def app_normal_results():
# Just some results, with different extensions and size, for good measure.
dupes = [
[no('foo1.ext1', size=1), no('foo2.ext2', size=2)],
]
return app_with_dupes(dupes)
@with_app(app_normal_results)
def test_kind_subcrit(app):
# The subcriteria of the "Kind" criteria is a list of extensions contained in the dupes.
app.select_pri_criterion("Kind")
eq_(app.pdialog.criteria_list, ['ext1', 'ext2'])

View File

@ -1,6 +1,5 @@
# Created By: Virgil Dupras
# Created On: 2006/02/23
# $Id$
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
@ -12,42 +11,14 @@ import os.path as op
from xml.etree import ElementTree as ET
from hscommon.path import Path
from hscommon.testutil import eq_
from hscommon.util import first
from . import engine_test, data
from . import data
from .. import engine
from .base import NamedObject, GetTestGroups
from ..results import Results
class NamedObject(engine_test.NamedObject):
path = property(lambda x:Path('basepath') + x.name)
is_ref = False
def __bool__(self):
return False #Make sure that operations are made correctly when the bool value of files is false.
@property
def folder_path(self):
return self.path[:-1]
# Returns a group set that looks like that:
# "foo bar" (1)
# "bar bleh" (1024)
# "foo bleh" (1)
# "ibabtu" (1)
# "ibabtu" (1)
def GetTestGroups():
objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")]
objects[1].size = 1024
matches = engine.getmatches(objects) #we should have 5 matches
groups = engine.get_groups(matches) #We should have 2 groups
for g in groups:
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
groups.sort(key=len, reverse=True) # We want the group with 3 members to be first.
return (objects,matches,groups)
class TestCaseResultsEmpty:
def setup_method(self, method):
self.results = Results(data)