1#!/usr/bin/env python
2
3from __future__ import print_function
4
5import os
6import os.path
7import shutil
8import subprocess
9import xattr
10
11import util
12
13# Notes:
14# * Due to internal buffer sizing, "large" should be at least 4096 bytes for
15#   uncompressed archives and archives with individual file compression, and
16#   and at least 32768 bytes for archives with compressed heaps.
17# * For large files, we intentionally use a non-base-2 file size to ensure we
18#   catch file extraction bugs for files with a size not equal to the internal
19#   read buffer size (which is base-2).
20# * For large extended attributes, we do the same thing.
21
22#
23# Utility Functions
24#
25
26class MissingExtendedAttributeError(AssertionError):
27	pass
28
29def _random_big_data(bytes=65536, path="/dev/random"):
30	"""
31	Returns a random string with the number of bytes requested. Due to xar
32	implementation details, this should be greater than 4096 (32768 for
33	compressed heap testing).
34
35	"""
36	with open(path, "r") as f:
37		return f.read(bytes)
38
39def _test_xattr_on_file_with_contents(filename, file_contents, xattrs=[], xar_create_flags=[], xar_extract_flags=[]):
40	try:
41		# Write file out
42		with open(filename, "w") as f:
43			f.write(file_contents)
44			for (key, value) in xattrs:
45				xattr.setxattr(f, key, value)
46
47		# Store it into a xarchive
48		archive_name = "{f}.xar".format(f=filename)
49		with util.archive_created(archive_name, filename, *xar_create_flags) as path:
50			# Extract xarchive
51			with util.directory_created("extracted") as directory:
52				# Validate resulting xattrs
53				subprocess.check_call(["xar", "-x", "-C", directory, "-f", path] + xar_extract_flags)
54				for (key, value) in xattrs:
55					try:
56						assert xattr.getxattr(os.path.join(directory, filename), key) == value, "extended attribute \"{n}\" has incorrect contents after extraction".format(n=key)
57					except KeyError:
58						raise MissingExtendedAttributeError("extended attribute \"{n}\" missing after extraction".format(n=key))
59
60				# Validate file contents
61				with open(os.path.join(directory, filename), "r") as f:
62					if f.read() != file_contents:
63						raise MissingExtendedAttributeError("archived file \"{f}\" has has incorrect contents after extraction".format(f=filename))
64	finally:
65		os.unlink(filename)
66
67
68#
69# Test Cases
70#
71
72# Note: xar currently drops empty extended attributes (and any xar_prop_t with empty contents, actually). The empty
73#       tests are commented out awaiting a day when this might be different.
74
75# def empty_xattr_empty_file(filename):
76# 	_test_xattr_on_file_with_contents(filename, "", xattrs=[("foo", "")])
77
78def small_xattr_empty_file(filename):
79	_test_xattr_on_file_with_contents(filename, "", xattrs=[("foo", "1234")])
80
81def large_xattr_empty_file(filename):
82	_test_xattr_on_file_with_contents(filename, "", xattrs=[("foo", _random_big_data(5000))])
83
84# def empty_xattr_small_file(filename):
85# 	_test_xattr_on_file_with_contents(filename, "small.file.contents", xattrs=[("foo", "")])
86
87def small_xattr_small_file(filename):
88	_test_xattr_on_file_with_contents(filename, "small.file.contents", xattrs=[("foo", "1234")])
89
90def large_xattr_small_file(filename):
91	_test_xattr_on_file_with_contents(filename, "small.file.contents", xattrs=[("foo", _random_big_data(4567))])
92
93# def empty_xattr_large_file(filename):
94# 	_test_xattr_on_file_with_contents(filename, _random_big_data(10000000), xattrs=[("foo", "")])
95
96def small_xattr_large_file(filename):
97	_test_xattr_on_file_with_contents(filename, _random_big_data(5000000), xattrs=[("foo", "1234")])
98
99def large_xattr_large_file(filename):
100	_test_xattr_on_file_with_contents(filename, _random_big_data(9876543), xattrs=[("foo", _random_big_data(6543))])
101
102def multiple_xattrs(filename):
103	_test_xattr_on_file_with_contents(filename, "", xattrs=[("foo", "bar"), ("baz", "1234"), ("quux", "more")]) # ("empty", "")
104
105def distribution_create(filename):
106	try:
107		_test_xattr_on_file_with_contents(filename, "dummy", xattrs=[("foo", "bar")], xar_create_flags=["--distribution"])
108	except MissingExtendedAttributeError:
109		pass
110	else:
111		raise AssertionError("no error encountered")
112
113# Note: xar currently has no way to exclude a property on extraction. This test is commented out awaiting the day
114#       when it can.
115
116# def distribution_extract(filename):
117# 	try:
118# 		_test_xattr_on_file_with_contents(filename, "dummy", xattrs=[("foo", "bar")], xar_extract_flags=["--distribution"])
119# 	except MissingExtendedAttributeError:
120# 		pass
121# 	else:
122# 		raise AssertionError("no error encountered")
123
124
125TEST_CASES = (small_xattr_empty_file, large_xattr_empty_file,
126              small_xattr_small_file, large_xattr_small_file,
127              small_xattr_large_file, large_xattr_large_file,
128              multiple_xattrs, distribution_create)
129
130if __name__ == "__main__":
131	for case in TEST_CASES:
132		try:
133			case(case.func_name)
134			print("PASSED: {f}".format(f=case.func_name))
135		except (AssertionError, IOError, subprocess.CalledProcessError):
136			import sys, os
137			print("FAILED: {f}".format(f=case.func_name))
138			sys.excepthook(*sys.exc_info())
139			print("")
140