diff options
Diffstat (limited to 'testing/mozbase/mozinfo')
-rw-r--r-- | testing/mozbase/mozinfo/mozinfo/__init__.py | 60 | ||||
-rwxr-xr-x | testing/mozbase/mozinfo/mozinfo/mozinfo.py | 300 | ||||
-rw-r--r-- | testing/mozbase/mozinfo/mozinfo/string_version.py | 43 | ||||
-rw-r--r-- | testing/mozbase/mozinfo/setup.py | 31 | ||||
-rw-r--r-- | testing/mozbase/mozinfo/tests/manifest.ini | 1 | ||||
-rw-r--r-- | testing/mozbase/mozinfo/tests/test.py | 121 |
6 files changed, 556 insertions, 0 deletions
diff --git a/testing/mozbase/mozinfo/mozinfo/__init__.py b/testing/mozbase/mozinfo/mozinfo/__init__.py new file mode 100644 index 000000000..7d0483cb5 --- /dev/null +++ b/testing/mozbase/mozinfo/mozinfo/__init__.py @@ -0,0 +1,60 @@ +# flake8: noqa +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import + +""" +interface to transform introspected system information to a format palatable to +Mozilla + +Module variables: + +.. attribute:: bits + + 32 or 64 + +.. attribute:: isBsd + + Returns ``True`` if the operating system is BSD + +.. attribute:: isLinux + + Returns ``True`` if the operating system is Linux + +.. attribute:: isMac + + Returns ``True`` if the operating system is Mac + +.. attribute:: isWin + + Returns ``True`` if the operating system is Windows + +.. attribute:: os + + Operating system [``'win'``, ``'mac'``, ``'linux'``, ...] + +.. attribute:: processor + + Processor architecture [``'x86'``, ``'x86_64'``, ``'ppc'``, ...] + +.. attribute:: version + + Operating system version string. For windows, the service pack information is also included + +.. attribute:: info + + Returns information identifying the current system. + + * :attr:`bits` + * :attr:`os` + * :attr:`processor` + * :attr:`version` + +""" + +from . import mozinfo +from .mozinfo import * + +__all__ = mozinfo.__all__ diff --git a/testing/mozbase/mozinfo/mozinfo/mozinfo.py b/testing/mozbase/mozinfo/mozinfo/mozinfo.py new file mode 100755 index 000000000..81a30307d --- /dev/null +++ b/testing/mozbase/mozinfo/mozinfo/mozinfo.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# TODO: it might be a good idea of adding a system name (e.g. 'Ubuntu' for +# linux) to the information; I certainly wouldn't want anyone parsing this +# information and having behaviour depend on it + +from __future__ import absolute_import + +import os +import platform +import re +import sys +from .string_version import StringVersion + + +# keep a copy of the os module since updating globals overrides this +_os = os + + +class unknown(object): + """marker class for unknown information""" + + def __nonzero__(self): + return False + + def __str__(self): + return 'UNKNOWN' +unknown = unknown() # singleton + + +def get_windows_version(): + import ctypes + + class OSVERSIONINFOEXW(ctypes.Structure): + _fields_ = [('dwOSVersionInfoSize', ctypes.c_ulong), + ('dwMajorVersion', ctypes.c_ulong), + ('dwMinorVersion', ctypes.c_ulong), + ('dwBuildNumber', ctypes.c_ulong), + ('dwPlatformId', ctypes.c_ulong), + ('szCSDVersion', ctypes.c_wchar * 128), + ('wServicePackMajor', ctypes.c_ushort), + ('wServicePackMinor', ctypes.c_ushort), + ('wSuiteMask', ctypes.c_ushort), + ('wProductType', ctypes.c_byte), + ('wReserved', ctypes.c_byte)] + + os_version = OSVERSIONINFOEXW() + os_version.dwOSVersionInfoSize = ctypes.sizeof(os_version) + retcode = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(os_version)) + if retcode != 0: + raise OSError + + return os_version.dwMajorVersion, os_version.dwMinorVersion, os_version.dwBuildNumber + +# get system information +info = {'os': unknown, + 'processor': unknown, + 'version': unknown, + 'os_version': unknown, + 'bits': unknown, + 'has_sandbox': unknown} +(system, node, release, version, machine, processor) = platform.uname() +(bits, linkage) = platform.architecture() + +# get os information and related data +if system in ["Microsoft", "Windows"]: + info['os'] = 'win' + # There is a Python bug on Windows to determine platform values + # http://bugs.python.org/issue7860 + if "PROCESSOR_ARCHITEW6432" in os.environ: + processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor) + else: + processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor) + system = os.environ.get("OS", system).replace('_', ' ') + (major, minor, _, _, service_pack) = os.sys.getwindowsversion() + info['service_pack'] = service_pack + if major >= 6 and minor >= 2: + # On windows >= 8.1 the system call that getwindowsversion uses has + # been frozen to always return the same values. In this case we call + # the RtlGetVersion API directly, which still provides meaningful + # values, at least for now. + major, minor, build_number = get_windows_version() + version = "%d.%d.%d" % (major, minor, build_number) + + os_version = "%d.%d" % (major, minor) +elif system.startswith('MINGW'): + # windows/mingw python build (msys) + info['os'] = 'win' + os_version = version = unknown +elif system == "Linux": + if hasattr(platform, "linux_distribution"): + (distro, os_version, codename) = platform.linux_distribution() + else: + (distro, os_version, codename) = platform.dist() + if not processor: + processor = machine + version = "%s %s" % (distro, os_version) + + # Bug in Python 2's `platform` library: + # It will return a triple of empty strings if the distribution is not supported. + # It works on Python 3. If we don't have an OS version, + # the unit tests fail to run. + if not distro and not os_version and not codename: + distro = 'lfs' + version = release + os_version = release + + info['os'] = 'linux' + info['linux_distro'] = distro +elif system in ['DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD']: + info['os'] = 'bsd' + version = os_version = sys.platform +elif system == "Darwin": + (release, versioninfo, machine) = platform.mac_ver() + version = "OS X %s" % release + versionNums = release.split('.')[:2] + os_version = "%s.%s" % (versionNums[0], versionNums[1]) + info['os'] = 'mac' +elif sys.platform in ('solaris', 'sunos5'): + info['os'] = 'unix' + os_version = version = sys.platform +else: + os_version = version = unknown + +info['version'] = version +info['os_version'] = StringVersion(os_version) + +# processor type and bits +if processor in ["i386", "i686"]: + if bits == "32bit": + processor = "x86" + elif bits == "64bit": + processor = "x86_64" +elif processor.upper() == "AMD64": + bits = "64bit" + processor = "x86_64" +elif processor == "Power Macintosh": + processor = "ppc" +bits = re.search('(\d+)bit', bits).group(1) +info.update({'processor': processor, + 'bits': int(bits), + }) + +if info['os'] == 'linux': + import ctypes + import errno + PR_SET_SECCOMP = 22 + SECCOMP_MODE_FILTER = 2 + ctypes.CDLL("libc.so.6", use_errno=True).prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0) + info['has_sandbox'] = ctypes.get_errno() == errno.EFAULT +else: + info['has_sandbox'] = True + +# standard value of choices, for easy inspection +choices = {'os': ['linux', 'bsd', 'win', 'mac', 'unix'], + 'bits': [32, 64], + 'processor': ['x86', 'x86_64', 'ppc']} + + +def sanitize(info): + """Do some sanitization of input values, primarily + to handle universal Mac builds.""" + if "processor" in info and info["processor"] == "universal-x86-x86_64": + # If we're running on OS X 10.6 or newer, assume 64-bit + if release[:4] >= "10.6": # Note this is a string comparison + info["processor"] = "x86_64" + info["bits"] = 64 + else: + info["processor"] = "x86" + info["bits"] = 32 + +# method for updating information + + +def update(new_info): + """ + Update the info. + + :param new_info: Either a dict containing the new info or a path/url + to a json file containing the new info. + """ + + if isinstance(new_info, basestring): + # lazy import + import mozfile + import json + f = mozfile.load(new_info) + new_info = json.loads(f.read()) + f.close() + + info.update(new_info) + sanitize(info) + globals().update(info) + + # convenience data for os access + for os_name in choices['os']: + globals()['is' + os_name.title()] = info['os'] == os_name + # unix is special + if isLinux or isBsd: # noqa + globals()['isUnix'] = True + + +def find_and_update_from_json(*dirs): + """ + Find a mozinfo.json file, load it, and update the info with the + contents. + + :param dirs: Directories in which to look for the file. They will be + searched after first looking in the root of the objdir + if the current script is being run from a Mozilla objdir. + + Returns the full path to mozinfo.json if it was found, or None otherwise. + """ + # First, see if we're in an objdir + try: + from mozbuild.base import MozbuildObject, BuildEnvironmentNotFoundException + build = MozbuildObject.from_environment() + json_path = _os.path.join(build.topobjdir, "mozinfo.json") + if _os.path.isfile(json_path): + update(json_path) + return json_path + except ImportError: + pass + except BuildEnvironmentNotFoundException: + pass + + for d in dirs: + d = _os.path.abspath(d) + json_path = _os.path.join(d, "mozinfo.json") + if _os.path.isfile(json_path): + update(json_path) + return json_path + + return None + + +def output_to_file(path): + import json + with open(path, 'w') as f: + f.write(json.dumps(info)) + +update({}) + +# exports +__all__ = info.keys() +__all__ += ['is' + os_name.title() for os_name in choices['os']] +__all__ += [ + 'info', + 'unknown', + 'main', + 'choices', + 'update', + 'find_and_update_from_json', + 'output_to_file', + 'StringVersion', +] + + +def main(args=None): + + # parse the command line + from optparse import OptionParser + parser = OptionParser(description=__doc__) + for key in choices: + parser.add_option('--%s' % key, dest=key, + action='store_true', default=False, + help="display choices for %s" % key) + options, args = parser.parse_args() + + # args are JSON blobs to override info + if args: + # lazy import + import json + for arg in args: + if _os.path.exists(arg): + string = file(arg).read() + else: + string = arg + update(json.loads(string)) + + # print out choices if requested + flag = False + for key, value in options.__dict__.items(): + if value is True: + print '%s choices: %s' % (key, ' '.join([str(choice) + for choice in choices[key]])) + flag = True + if flag: + return + + # otherwise, print out all info + for key, value in info.items(): + print '%s: %s' % (key, value) + +if __name__ == '__main__': + main() diff --git a/testing/mozbase/mozinfo/mozinfo/string_version.py b/testing/mozbase/mozinfo/mozinfo/string_version.py new file mode 100644 index 000000000..fd77fa566 --- /dev/null +++ b/testing/mozbase/mozinfo/mozinfo/string_version.py @@ -0,0 +1,43 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from distutils.version import LooseVersion + + +class StringVersion(str): + """ + A string version that can be compared with comparison operators. + """ + + def __init__(self, vstring): + str.__init__(self, vstring) + self.version = LooseVersion(vstring) + + def __repr__(self): + return "StringVersion ('%s')" % self + + def __to_version(self, other): + if not isinstance(other, StringVersion): + other = StringVersion(other) + return other.version + + # rich comparison methods + + def __lt__(self, other): + return self.version < self.__to_version(other) + + def __le__(self, other): + return self.version <= self.__to_version(other) + + def __eq__(self, other): + return self.version == self.__to_version(other) + + def __ne__(self, other): + return self.version != self.__to_version(other) + + def __gt__(self, other): + return self.version > self.__to_version(other) + + def __ge__(self, other): + return self.version >= self.__to_version(other) diff --git a/testing/mozbase/mozinfo/setup.py b/testing/mozbase/mozinfo/setup.py new file mode 100644 index 000000000..3e76b9db4 --- /dev/null +++ b/testing/mozbase/mozinfo/setup.py @@ -0,0 +1,31 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from setuptools import setup + +PACKAGE_VERSION = '0.9' + +# dependencies +deps = ['mozfile >= 0.12'] + +setup(name='mozinfo', + version=PACKAGE_VERSION, + description="Library to get system information for use in Mozilla testing", + long_description="see http://mozbase.readthedocs.org", + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='mozilla', + author='Mozilla Automation and Testing Team', + author_email='tools@lists.mozilla.org', + url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase', + license='MPL', + packages=['mozinfo'], + include_package_data=True, + zip_safe=False, + install_requires=deps, + entry_points=""" + # -*- Entry points: -*- + [console_scripts] + mozinfo = mozinfo:main + """, + ) diff --git a/testing/mozbase/mozinfo/tests/manifest.ini b/testing/mozbase/mozinfo/tests/manifest.ini new file mode 100644 index 000000000..528fdea7b --- /dev/null +++ b/testing/mozbase/mozinfo/tests/manifest.ini @@ -0,0 +1 @@ +[test.py] diff --git a/testing/mozbase/mozinfo/tests/test.py b/testing/mozbase/mozinfo/tests/test.py new file mode 100644 index 000000000..b9457cff9 --- /dev/null +++ b/testing/mozbase/mozinfo/tests/test.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import mock +import os +import shutil +import sys +import tempfile +import unittest +import mozinfo + + +class TestMozinfo(unittest.TestCase): + + def setUp(self): + reload(mozinfo) + self.tempdir = os.path.abspath(tempfile.mkdtemp()) + + # When running from an objdir mozinfo will use a build generated json file + # instead of the ones created for testing. Prevent that from happening. + # See bug 896038 for details. + sys.modules['mozbuild'] = None + + def tearDown(self): + shutil.rmtree(self.tempdir) + del sys.modules['mozbuild'] + + def test_basic(self): + """Test that mozinfo has a few attributes.""" + self.assertNotEqual(mozinfo.os, None) + # should have isFoo == True where os == "foo" + self.assertTrue(getattr(mozinfo, "is" + mozinfo.os[0].upper() + mozinfo.os[1:])) + + def test_update(self): + """Test that mozinfo.update works.""" + mozinfo.update({"foo": 123}) + self.assertEqual(mozinfo.info["foo"], 123) + + def test_update_file(self): + """Test that mozinfo.update can load a JSON file.""" + j = os.path.join(self.tempdir, "mozinfo.json") + with open(j, "w") as f: + f.write(json.dumps({"foo": "xyz"})) + mozinfo.update(j) + self.assertEqual(mozinfo.info["foo"], "xyz") + + def test_update_file_invalid_json(self): + """Test that mozinfo.update handles invalid JSON correctly""" + j = os.path.join(self.tempdir, 'test.json') + with open(j, 'w') as f: + f.write('invalid{"json":') + self.assertRaises(ValueError, mozinfo.update, [j]) + + def test_find_and_update_file(self): + """Test that mozinfo.find_and_update_from_json can + find mozinfo.json in a directory passed to it.""" + j = os.path.join(self.tempdir, "mozinfo.json") + with open(j, "w") as f: + f.write(json.dumps({"foo": "abcdefg"})) + self.assertEqual(mozinfo.find_and_update_from_json(self.tempdir), j) + self.assertEqual(mozinfo.info["foo"], "abcdefg") + + def test_find_and_update_file_invalid_json(self): + """Test that mozinfo.find_and_update_from_json can + handle invalid JSON""" + j = os.path.join(self.tempdir, "mozinfo.json") + with open(j, 'w') as f: + f.write('invalid{"json":') + self.assertRaises(ValueError, mozinfo.find_and_update_from_json, self.tempdir) + + def test_find_and_update_file_mozbuild(self): + """Test that mozinfo.find_and_update_from_json can + find mozinfo.json using the mozbuild module.""" + j = os.path.join(self.tempdir, "mozinfo.json") + with open(j, "w") as f: + f.write(json.dumps({"foo": "123456"})) + m = mock.MagicMock() + # Mock the value of MozbuildObject.from_environment().topobjdir. + m.MozbuildObject.from_environment.return_value.topobjdir = self.tempdir + with mock.patch.dict(sys.modules, {"mozbuild": m, "mozbuild.base": m}): + self.assertEqual(mozinfo.find_and_update_from_json(), j) + self.assertEqual(mozinfo.info["foo"], "123456") + + def test_output_to_file(self): + """Test that mozinfo.output_to_file works.""" + path = os.path.join(self.tempdir, "mozinfo.json") + mozinfo.output_to_file(path) + self.assertEqual(open(path).read(), json.dumps(mozinfo.info)) + + +class TestStringVersion(unittest.TestCase): + + def test_os_version_is_a_StringVersion(self): + self.assertIsInstance(mozinfo.os_version, mozinfo.StringVersion) + + def test_compare_to_string(self): + version = mozinfo.StringVersion('10.10') + + self.assertGreater(version, '10.2') + self.assertGreater('11', version) + self.assertGreaterEqual(version, '10.10') + self.assertGreaterEqual('10.11', version) + self.assertEqual(version, '10.10') + self.assertEqual('10.10', version) + self.assertNotEqual(version, '10.2') + self.assertNotEqual('11', version) + self.assertLess(version, '11.8.5') + self.assertLess('10.2', version) + self.assertLessEqual(version, '11') + self.assertLessEqual('10.10', version) + + def test_to_string(self): + self.assertEqual('10.10', str(mozinfo.StringVersion('10.10'))) + + +if __name__ == '__main__': + unittest.main() |