diff --git a/lib/iris/io/__init__.py b/lib/iris/io/__init__.py index 3f148096f2..620e9a1919 100644 --- a/lib/iris/io/__init__.py +++ b/lib/iris/io/__init__.py @@ -148,23 +148,26 @@ def expand_filespecs(file_specs): File paths which may contain '~' elements or wildcards. Returns: - A list of matching file paths. If any of the file-specs matches no + A list of matching absolute file paths. If any of the file-specs matches no existing files, an exception is raised. """ - # Remove any hostname component - currently unused - filenames = [os.path.expanduser(fn[2:] if fn.startswith('//') else fn) + # Remove any hostname component - currently unused - expand paths as absolutes + filenames = [os.path.abspath(os.path.expanduser(fn[2:] if fn.startswith('//') else fn)) for fn in file_specs] # Try to expand all filenames as globs glob_expanded = {fn : sorted(glob.glob(fn)) for fn in filenames} # If any of the specs expanded to an empty list then raise an error - value_lists = glob_expanded.values() + value_list_raw = glob_expanded.values() + + # Here is a term to sort value_lists alphabetically, for consistency + value_lists = sorted(value_list_raw) if not all(value_lists): raise IOError("One or more of the files specified did not exist %s." % ["%s expanded to %s" % (pattern, expanded if expanded else "empty") - for pattern, expanded in six.iteritems(glob_expanded)]) + for pattern, expanded in sorted(six.iteritems(glob_expanded))]) return sum(value_lists, []) diff --git a/lib/iris/tests/unit/io/test_expand_filespecs.py b/lib/iris/tests/unit/io/test_expand_filespecs.py new file mode 100644 index 0000000000..79457d9c09 --- /dev/null +++ b/lib/iris/tests/unit/io/test_expand_filespecs.py @@ -0,0 +1,75 @@ +# (C) British Crown Copyright 2016, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Unit tests for the `iris.io.expand_filespecs` function.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +import os +import tempfile +import shutil + +import iris.io as iio + + +class TestExpandFilespecs(tests.IrisTest): + + def setUp(self): + tests.IrisTest.setUp(self) + self.tmpdir = tempfile.mkdtemp() + self.fnames = ['a.foo', 'b.txt'] + for fname in self.fnames: + with open(os.path.join(self.tmpdir, fname), 'w') as fh: + fh.write('anything') + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def test_absolute_path(self): + result = iio.expand_filespecs([os.path.join(self.tmpdir, '*')]) + expected = [os.path.join(self.tmpdir, fname) for fname in self.fnames] + self.assertEqual(result, expected) + + def test_double_slash(self): + product = iio.expand_filespecs(['//' + os.path.join(self.tmpdir, '*')]) + predicted = [os.path.join(self.tmpdir, fname) for fname in self.fnames] + self.assertEqual(product, predicted) + + def test_relative_path(self): + os.chdir(self.tmpdir) + item_out = iio.expand_filespecs(['*']) + item_in = [os.path.join(self.tmpdir, fname) for fname in self.fnames] + self.assertEqual(item_out, item_in) + + def test_no_files_found(self): + msg = 'b expanded to empty' + with self.assertRaisesRegexp(IOError, msg): + iio.expand_filespecs([self.tmpdir + '_b']) + + def test_files_and_none(self): + msg = 'b expanded to empty.*expanded to .*b.txt' + with self.assertRaisesRegexp(IOError, msg): + iio.expand_filespecs([self.tmpdir + '_b', + os.path.join(self.tmpdir, '*')]) + + +if __name__ == "__main__": + tests.main()