Bug 1168607 - Add get method to finders; r=glandium
authorGregory Szorc <gps@mozilla.com>
Thu, 04 Jun 2015 11:24:03 -0700
changeset 247844 187dfb251703ddce7ffbb553850efcabf3bf8774
parent 247843 553615c0705ea3aa76fea98e3b4b14dcbabcc760
child 247845 1a470efc9fc2b3d76831f683dc2eae1c143628c1
push id28885
push usercbook@mozilla.com
push dateWed, 10 Jun 2015 13:18:59 +0000
treeherderautoland@e101c589c242 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs1168607
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1168607 - Add get method to finders; r=glandium Today, the *Finder classes are optimized for doing matching and returning multiple results. However, sometimes only looking for a single file is wanted. This commit implements the "get" method on all the *Finder classes. It returns a BaseFile or None. FileFinder._find_file has been refactored into FileFinder.get to avoid code duplication.
python/mozbuild/mozpack/files.py
python/mozbuild/mozpack/test/test_files.py
--- a/python/mozbuild/mozpack/files.py
+++ b/python/mozbuild/mozpack/files.py
@@ -708,16 +708,30 @@ class BaseFinder(object):
         mozpack.path.match documentation for a description of the handled
         patterns.
         '''
         while pattern.startswith('/'):
             pattern = pattern[1:]
         for p, f in self._find(pattern):
             yield p, self._minify_file(p, f)
 
+    def get(self, path):
+        """Obtain a single file.
+
+        Where ``find`` is tailored towards matching multiple files, this method
+        is used for retrieving a single file. Use this method when performance
+        is critical.
+
+        Returns a ``BaseFile`` if at most one file exists or ``None`` otherwise.
+        """
+        files = list(self.find(path))
+        if len(files) != 1:
+            return None
+        return files[0][1]
+
     def __iter__(self):
         '''
         Iterates over all files under the base directory (excluding files
         starting with a '.' and files at any level under a directory starting
         with a '.').
             for path, file in finder:
                 ...
         '''
@@ -783,17 +797,18 @@ class FileFinder(BaseFinder):
         Note all files with a name starting with a '.' are ignored when
         scanning directories, but are not ignored when explicitely requested.
         '''
         if '*' in pattern:
             return self._find_glob('', mozpath.split(pattern))
         elif os.path.isdir(os.path.join(self.base, pattern)):
             return self._find_dir(pattern)
         else:
-            return self._find_file(pattern)
+            f = self.get(pattern)
+            return ((pattern, f),) if f else ()
 
     def _find_dir(self, path):
         '''
         Actual implementation of FileFinder.find() when the given pattern
         corresponds to an existing directory under the base directory.
         Ignores file names starting with a '.' under the given path. If the
         path itself has leafs starting with a '.', they are not ignored.
         '''
@@ -805,33 +820,29 @@ class FileFinder(BaseFinder):
         # likely dependent on filesystem implementation details, such as
         # inode ordering.
         for p in sorted(os.listdir(os.path.join(self.base, path))):
             if p.startswith('.'):
                 continue
             for p_, f in self._find(mozpath.join(path, p)):
                 yield p_, f
 
-    def _find_file(self, path):
-        '''
-        Actual implementation of FileFinder.find() when the given pattern
-        corresponds to an existing file under the base directory.
-        '''
+    def get(self, path):
         srcpath = os.path.join(self.base, path)
         if not os.path.exists(srcpath):
-            return
+            return None
 
         for p in self.ignore:
             if mozpath.match(path, p):
-                return
+                return None
 
         if self.find_executables and is_executable(srcpath):
-            yield path, ExecutableFile(srcpath)
+            return ExecutableFile(srcpath)
         else:
-            yield path, File(srcpath)
+            return File(srcpath)
 
     def _find_glob(self, base, pattern):
         '''
         Actual implementation of FileFinder.find() when the given pattern
         contains globbing patterns ('*' or '**'). This is meant to be an
         equivalent of:
             for p, f in self:
                 if mozpath.match(p, pattern):
--- a/python/mozbuild/mozpack/test/test_files.py
+++ b/python/mozbuild/mozpack/test/test_files.py
@@ -906,16 +906,26 @@ class TestFileFinder(MatchTestTemplate, 
         do_check(self, self.finder, pattern, result)
 
     def test_file_finder(self):
         self.prepare_match_test(with_dotfiles=True)
         self.finder = FileFinder(self.tmpdir)
         self.do_match_test()
         self.do_finder_test(self.finder)
 
+    def test_get(self):
+        self.prepare_match_test()
+        finder = FileFinder(self.tmpdir)
+
+        self.assertIsNone(finder.get('does-not-exist'))
+        res = finder.get('bar')
+        self.assertIsInstance(res, File)
+        self.assertEqual(mozpath.normpath(res.path),
+                         mozpath.join(self.tmpdir, 'bar'))
+
     def test_ignored_dirs(self):
         """Ignored directories should not have results returned."""
         self.prepare_match_test()
         self.add('fooz')
 
         # Present to ensure prefix matching doesn't exclude.
         self.add('foo/quxz')
 
@@ -964,16 +974,19 @@ class TestJarFinder(MatchTestTemplate, T
     def test_jar_finder(self):
         self.jar = JarWriter(file=self.tmppath('test.jar'))
         self.prepare_match_test()
         self.jar.finish()
         reader = JarReader(file=self.tmppath('test.jar'))
         self.finder = JarFinder(self.tmppath('test.jar'), reader)
         self.do_match_test()
 
+        self.assertIsNone(self.finder.get('does-not-exist'))
+        self.assertIsInstance(self.finder.get('bar'), DeflatedFile)
+
 
 class TestComposedFinder(MatchTestTemplate, TestWithTmpDir):
     def add(self, path, content=None):
         # Put foo/qux files under $tmp/b.
         if path.startswith('foo/qux/'):
             real_path = mozpath.join('b', path[8:])
         else:
             real_path = mozpath.join('a', path)
@@ -995,11 +1008,14 @@ class TestComposedFinder(MatchTestTempla
         open(self.tmppath('a/foo/qux/hoge'), 'wb').write('hoge')
         open(self.tmppath('a/foo/qux/bar'), 'wb').write('not the right content')
         self.finder = ComposedFinder({
             '': FileFinder(self.tmppath('a')),
             'foo/qux': FileFinder(self.tmppath('b')),
         })
         self.do_match_test()
 
+        self.assertIsNone(self.finder.get('does-not-exist'))
+        self.assertIsInstance(self.finder.get('bar'), File)
+
 
 if __name__ == '__main__':
     mozunit.main()