bpo-23205: IDLE: Add tests and refactor grep's findfiles (GH-12203)
* Add tests for grep findfiles. * Move findfiles to module function. * Change findfiles to use os.walk. Based on a patch by Al Sweigart.
This commit is contained in:
parent
6d5ee973f0
commit
d60f658fc0
|
@ -40,6 +40,27 @@ def grep(text, io=None, flist=None):
|
||||||
dialog.open(text, searchphrase, io)
|
dialog.open(text, searchphrase, io)
|
||||||
|
|
||||||
|
|
||||||
|
def walk_error(msg):
|
||||||
|
"Handle os.walk error."
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def findfiles(folder, pattern, recursive):
|
||||||
|
"""Generate file names in dir that match pattern.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder: Root directory to search.
|
||||||
|
pattern: File pattern to match.
|
||||||
|
recursive: True to include subdirectories.
|
||||||
|
"""
|
||||||
|
for dirpath, _, filenames in os.walk(folder, onerror=walk_error):
|
||||||
|
yield from (os.path.join(dirpath, name)
|
||||||
|
for name in filenames
|
||||||
|
if fnmatch.fnmatch(name, pattern))
|
||||||
|
if not recursive:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class GrepDialog(SearchDialogBase):
|
class GrepDialog(SearchDialogBase):
|
||||||
"Dialog for searching multiple files."
|
"Dialog for searching multiple files."
|
||||||
|
|
||||||
|
@ -140,15 +161,16 @@ class GrepDialog(SearchDialogBase):
|
||||||
prog: The compiled, cooked search pattern.
|
prog: The compiled, cooked search pattern.
|
||||||
path: String containing the search path.
|
path: String containing the search path.
|
||||||
"""
|
"""
|
||||||
dir, base = os.path.split(path)
|
folder, filepat = os.path.split(path)
|
||||||
list = self.findfiles(dir, base, self.recvar.get())
|
if not folder:
|
||||||
list.sort()
|
folder = os.curdir
|
||||||
|
filelist = sorted(findfiles(folder, filepat, self.recvar.get()))
|
||||||
self.close()
|
self.close()
|
||||||
pat = self.engine.getpat()
|
pat = self.engine.getpat()
|
||||||
print(f"Searching {pat!r} in {path} ...")
|
print(f"Searching {pat!r} in {path} ...")
|
||||||
hits = 0
|
hits = 0
|
||||||
try:
|
try:
|
||||||
for fn in list:
|
for fn in filelist:
|
||||||
try:
|
try:
|
||||||
with open(fn, errors='replace') as f:
|
with open(fn, errors='replace') as f:
|
||||||
for lineno, line in enumerate(f, 1):
|
for lineno, line in enumerate(f, 1):
|
||||||
|
@ -166,36 +188,6 @@ class GrepDialog(SearchDialogBase):
|
||||||
# so in OW.write, OW.text.insert fails.
|
# so in OW.write, OW.text.insert fails.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def findfiles(self, dir, base, rec):
|
|
||||||
"""Return list of files in the dir that match the base pattern.
|
|
||||||
|
|
||||||
Use the current directory if dir has no value.
|
|
||||||
If rec is True, recursively iterate through subdirectories.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dir: Directory path to search.
|
|
||||||
base: File search pattern.
|
|
||||||
rec: Boolean for recursive search through subdirectories.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
names = os.listdir(dir or os.curdir)
|
|
||||||
except OSError as msg:
|
|
||||||
print(msg)
|
|
||||||
return []
|
|
||||||
list = []
|
|
||||||
subdirs = []
|
|
||||||
for name in names:
|
|
||||||
fn = os.path.join(dir, name)
|
|
||||||
if os.path.isdir(fn):
|
|
||||||
subdirs.append(fn)
|
|
||||||
else:
|
|
||||||
if fnmatch.fnmatch(name, base):
|
|
||||||
list.append(fn)
|
|
||||||
if rec:
|
|
||||||
for subdir in subdirs:
|
|
||||||
list.extend(self.findfiles(subdir, base, rec))
|
|
||||||
return list
|
|
||||||
|
|
||||||
|
|
||||||
def _grep_dialog(parent): # htest #
|
def _grep_dialog(parent): # htest #
|
||||||
from tkinter import Toplevel, Text, SEL, END
|
from tkinter import Toplevel, Text, SEL, END
|
||||||
|
|
|
@ -5,10 +5,11 @@ An exception raised in one method will fail callers.
|
||||||
Otherwise, tests are mostly independent.
|
Otherwise, tests are mostly independent.
|
||||||
Currently only test grep_it, coverage 51%.
|
Currently only test grep_it, coverage 51%.
|
||||||
"""
|
"""
|
||||||
from idlelib.grep import GrepDialog
|
from idlelib import grep
|
||||||
import unittest
|
import unittest
|
||||||
from test.support import captured_stdout
|
from test.support import captured_stdout
|
||||||
from idlelib.idle_test.mock_tk import Var
|
from idlelib.idle_test.mock_tk import Var
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,23 +27,92 @@ searchengine = Dummy_searchengine()
|
||||||
class Dummy_grep:
|
class Dummy_grep:
|
||||||
# Methods tested
|
# Methods tested
|
||||||
#default_command = GrepDialog.default_command
|
#default_command = GrepDialog.default_command
|
||||||
grep_it = GrepDialog.grep_it
|
grep_it = grep.GrepDialog.grep_it
|
||||||
findfiles = GrepDialog.findfiles
|
|
||||||
# Other stuff needed
|
# Other stuff needed
|
||||||
recvar = Var(False)
|
recvar = Var(False)
|
||||||
engine = searchengine
|
engine = searchengine
|
||||||
def close(self): # gui method
|
def close(self): # gui method
|
||||||
pass
|
pass
|
||||||
|
|
||||||
grep = Dummy_grep()
|
_grep = Dummy_grep()
|
||||||
|
|
||||||
|
|
||||||
class FindfilesTest(unittest.TestCase):
|
class FindfilesTest(unittest.TestCase):
|
||||||
# findfiles is really a function, not a method, could be iterator
|
|
||||||
# test that filename return filename
|
@classmethod
|
||||||
# test that idlelib has many .py files
|
def setUpClass(cls):
|
||||||
# test that recursive flag adds idle_test .py files
|
cls.realpath = os.path.realpath(__file__)
|
||||||
pass
|
cls.path = os.path.dirname(cls.realpath)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
del cls.realpath, cls.path
|
||||||
|
|
||||||
|
def test_invaliddir(self):
|
||||||
|
with captured_stdout() as s:
|
||||||
|
filelist = list(grep.findfiles('invaliddir', '*.*', False))
|
||||||
|
self.assertEqual(filelist, [])
|
||||||
|
self.assertIn('invalid', s.getvalue())
|
||||||
|
|
||||||
|
def test_curdir(self):
|
||||||
|
# Test os.curdir.
|
||||||
|
ff = grep.findfiles
|
||||||
|
save_cwd = os.getcwd()
|
||||||
|
os.chdir(self.path)
|
||||||
|
filename = 'test_grep.py'
|
||||||
|
filelist = list(ff(os.curdir, filename, False))
|
||||||
|
self.assertIn(os.path.join(os.curdir, filename), filelist)
|
||||||
|
os.chdir(save_cwd)
|
||||||
|
|
||||||
|
def test_base(self):
|
||||||
|
ff = grep.findfiles
|
||||||
|
readme = os.path.join(self.path, 'README.txt')
|
||||||
|
|
||||||
|
# Check for Python files in path where this file lives.
|
||||||
|
filelist = list(ff(self.path, '*.py', False))
|
||||||
|
# This directory has many Python files.
|
||||||
|
self.assertGreater(len(filelist), 10)
|
||||||
|
self.assertIn(self.realpath, filelist)
|
||||||
|
self.assertNotIn(readme, filelist)
|
||||||
|
|
||||||
|
# Look for .txt files in path where this file lives.
|
||||||
|
filelist = list(ff(self.path, '*.txt', False))
|
||||||
|
self.assertNotEqual(len(filelist), 0)
|
||||||
|
self.assertNotIn(self.realpath, filelist)
|
||||||
|
self.assertIn(readme, filelist)
|
||||||
|
|
||||||
|
# Look for non-matching pattern.
|
||||||
|
filelist = list(ff(self.path, 'grep.*', False))
|
||||||
|
self.assertEqual(len(filelist), 0)
|
||||||
|
self.assertNotIn(self.realpath, filelist)
|
||||||
|
|
||||||
|
def test_recurse(self):
|
||||||
|
ff = grep.findfiles
|
||||||
|
parent = os.path.dirname(self.path)
|
||||||
|
grepfile = os.path.join(parent, 'grep.py')
|
||||||
|
pat = '*.py'
|
||||||
|
|
||||||
|
# Get Python files only in parent directory.
|
||||||
|
filelist = list(ff(parent, pat, False))
|
||||||
|
parent_size = len(filelist)
|
||||||
|
# Lots of Python files in idlelib.
|
||||||
|
self.assertGreater(parent_size, 20)
|
||||||
|
self.assertIn(grepfile, filelist)
|
||||||
|
# Without subdirectories, this file isn't returned.
|
||||||
|
self.assertNotIn(self.realpath, filelist)
|
||||||
|
|
||||||
|
# Include subdirectories.
|
||||||
|
filelist = list(ff(parent, pat, True))
|
||||||
|
# More files found now.
|
||||||
|
self.assertGreater(len(filelist), parent_size)
|
||||||
|
self.assertIn(grepfile, filelist)
|
||||||
|
# This file exists in list now.
|
||||||
|
self.assertIn(self.realpath, filelist)
|
||||||
|
|
||||||
|
# Check another level up the tree.
|
||||||
|
parent = os.path.dirname(parent)
|
||||||
|
filelist = list(ff(parent, '*.py', True))
|
||||||
|
self.assertIn(self.realpath, filelist)
|
||||||
|
|
||||||
|
|
||||||
class Grep_itTest(unittest.TestCase):
|
class Grep_itTest(unittest.TestCase):
|
||||||
|
@ -51,9 +121,9 @@ class Grep_itTest(unittest.TestCase):
|
||||||
# from incomplete replacement, so 'later'.
|
# from incomplete replacement, so 'later'.
|
||||||
|
|
||||||
def report(self, pat):
|
def report(self, pat):
|
||||||
grep.engine._pat = pat
|
_grep.engine._pat = pat
|
||||||
with captured_stdout() as s:
|
with captured_stdout() as s:
|
||||||
grep.grep_it(re.compile(pat), __file__)
|
_grep.grep_it(re.compile(pat), __file__)
|
||||||
lines = s.getvalue().split('\n')
|
lines = s.getvalue().split('\n')
|
||||||
lines.pop() # remove bogus '' after last \n
|
lines.pop() # remove bogus '' after last \n
|
||||||
return lines
|
return lines
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
For the grep module, add tests for findfiles, refactor findfiles to be a
|
||||||
|
module-level function, and refactor findfiles to use os.walk.
|
Loading…
Reference in New Issue