diff options
author | Tim Burke <tim.burke@gmail.com> | 2021-08-27 15:44:42 -0700 |
---|---|---|
committer | Tim Burke <tim.burke@gmail.com> | 2021-08-27 15:51:51 -0700 |
commit | 5c06ab1418daa853575106073cd67ed0fec6ce99 (patch) | |
tree | 2924c95456f61edec8315202a7944ec814a6362a | |
parent | 48f7881ab24e5acfb4d16c4bb6137b918b8a9393 (diff) | |
download | xattr-5c06ab1418daa853575106073cd67ed0fec6ce99.tar.gz |
Add tests for xattr.tool
As a side-effect, make it easier to run xattr.tool.main from tests.
-rw-r--r-- | xattr/tests/test_tool.py | 117 | ||||
-rwxr-xr-x | xattr/tool.py | 28 |
2 files changed, 131 insertions, 14 deletions
diff --git a/xattr/tests/test_tool.py b/xattr/tests/test_tool.py new file mode 100644 index 0000000..644364f --- /dev/null +++ b/xattr/tests/test_tool.py @@ -0,0 +1,117 @@ +import contextlib +import errno +import io +import os +import shutil +import sys +import tempfile +import unittest +import uuid + +import xattr +import xattr.tool + + +class TestTool(unittest.TestCase): + def setUp(self): + self.test_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.test_dir) + + orig_stdout = sys.stdout + + def unpatch_stdout(sys=sys, orig_stdout=orig_stdout): + sys.stdout = orig_stdout + + self.addCleanup(unpatch_stdout) + sys.stdout = self.mock_stdout = io.StringIO() + + def getoutput(self): + value = self.mock_stdout.getvalue() + self.mock_stdout.seek(0) + self.mock_stdout.truncate(0) + return value + + @contextlib.contextmanager + def temp_file(self): + test_file = os.path.join(self.test_dir, str(uuid.uuid4())) + fd = os.open(test_file, os.O_CREAT | os.O_WRONLY) + try: + yield test_file, fd + finally: + os.close(fd) + + def set_xattr(self, fd, name, value): + try: + xattr.setxattr(fd, name, value) + except OSError as e: + if e.errno == errno.ENOTSUP: + raise unittest.SkipTest('xattrs are not supported') + raise + + def test_utf8(self): + with self.temp_file() as (file_path, fd): + self.set_xattr(fd, 'user.test-utf8', + u'\N{SNOWMAN}'.encode('utf8')) + self.set_xattr(fd, 'user.test-utf8-and-nul', + u'\N{SNOWMAN}\0'.encode('utf8')) + + xattr.tool.main(['prog', '-p', 'user.test-utf8', file_path]) + self.assertEqual(u'\N{SNOWMAN}\n', self.getoutput()) + + xattr.tool.main(['prog', '-p', 'user.test-utf8-and-nul', file_path]) + self.assertEqual(u''' +0000 E2 98 83 00 .... + +'''.lstrip(), self.getoutput()) + + xattr.tool.main(['prog', '-l', file_path]) + output = self.getoutput() + self.assertIn(u'user.test-utf8: \N{SNOWMAN}\n', output) + self.assertIn(u''' +user.test-utf8-and-nul: +0000 E2 98 83 00 .... + +'''.lstrip(), output) + + def test_non_utf8(self): + with self.temp_file() as (file_path, fd): + self.set_xattr(fd, 'user.test-not-utf8', b'cannot\xffdecode') + + xattr.tool.main(['prog', '-p', 'user.test-not-utf8', file_path]) + self.assertEqual(u''' +0000 63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65 cannot.decode + +'''.lstrip(), self.getoutput()) + + xattr.tool.main(['prog', '-l', file_path]) + self.assertIn(u''' +user.test-not-utf8: +0000 63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65 cannot.decode + +'''.lstrip(), self.getoutput()) + + def test_nul(self): + with self.temp_file() as (file_path, fd): + self.set_xattr(fd, 'user.test', b'foo\0bar') + self.set_xattr(fd, 'user.test-long', + b'some rather long value with\0nul\0chars in it') + + xattr.tool.main(['prog', '-p', 'user.test', file_path]) + self.assertEqual(u''' +0000 66 6F 6F 00 62 61 72 foo.bar + +'''.lstrip(), self.getoutput()) + + xattr.tool.main(['prog', '-p', 'user.test-long', file_path]) + self.assertEqual(u''' +0000 73 6F 6D 65 20 72 61 74 68 65 72 20 6C 6F 6E 67 some rather long +0010 20 76 61 6C 75 65 20 77 69 74 68 00 6E 75 6C 00 value with.nul. +0020 63 68 61 72 73 20 69 6E 20 69 74 chars in it + +'''.lstrip(), self.getoutput()) + + xattr.tool.main(['prog', '-l', file_path]) + self.assertIn(u''' +0000 66 6F 6F 00 62 61 72 foo.bar + +'''.lstrip(), self.getoutput()) diff --git a/xattr/tool.py b/xattr/tool.py index ac12654..0623560 100755 --- a/xattr/tool.py +++ b/xattr/tool.py @@ -62,9 +62,9 @@ def usage(e=None): print(" -z: compress or decompress (if compressed) attribute value in zip format") if e: - sys.exit(64) + return 64 else: - sys.exit(0) + return 0 if sys.version_info < (3,): @@ -87,11 +87,11 @@ def _dump(src, length=16): return ''.join(result) -def main(): +def main(argv): try: - (optargs, args) = getopt.getopt(sys.argv[1:], "hlpwdzs", ["help"]) + (optargs, args) = getopt.getopt(argv[1:], "hlpwdzs", ["help"]) except getopt.GetoptError as e: - usage(e) + return usage(e) attr_name = None long_format = False @@ -105,7 +105,7 @@ def main(): for opt, arg in optargs: if opt in ("-h", "--help"): - usage() + return usage() elif opt == "-l": long_format = True elif opt == "-s": @@ -113,31 +113,31 @@ def main(): elif opt == "-p": read = True if write or delete: - usage("-p not allowed with -w or -d") + return usage("-p not allowed with -w or -d") elif opt == "-w": write = True if read or delete: - usage("-w not allowed with -p or -d") + return usage("-w not allowed with -p or -d") elif opt == "-d": delete = True if read or write: - usage("-d not allowed with -p or -w") + return usage("-d not allowed with -p or -w") elif opt == "-z": compress = zlib.compress decompress = zlib.decompress if write or delete: if long_format: - usage("-l not allowed with -w or -p") + return usage("-l not allowed with -w or -p") if read or write or delete: if not args: - usage("No attr_name") + return usage("No attr_name") attr_name = args.pop(0) if write: if not args: - usage("No attr_value") + return usage("No attr_value") attr_value = args.pop(0).encode('utf-8') if len(args) > 1: @@ -230,7 +230,7 @@ def main(): else: print("".join((file_prefix, attr_name))) - sys.exit(status) + return status if __name__ == "__main__": - main() + sys.exit(main(sys.argv)) |