summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozversion
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/mozversion')
-rw-r--r--testing/mozbase/mozversion/mozversion/__init__.py7
-rw-r--r--testing/mozbase/mozversion/mozversion/errors.py30
-rw-r--r--testing/mozbase/mozversion/mozversion/mozversion.py340
-rw-r--r--testing/mozbase/mozversion/setup.py29
-rw-r--r--testing/mozbase/mozversion/tests/manifest.ini4
-rw-r--r--testing/mozbase/mozversion/tests/test_apk.py43
-rw-r--r--testing/mozbase/mozversion/tests/test_b2g.py75
-rw-r--r--testing/mozbase/mozversion/tests/test_binary.py177
-rw-r--r--testing/mozbase/mozversion/tests/test_sources.py85
9 files changed, 790 insertions, 0 deletions
diff --git a/testing/mozbase/mozversion/mozversion/__init__.py b/testing/mozbase/mozversion/mozversion/__init__.py
new file mode 100644
index 000000000..7894bcb9c
--- /dev/null
+++ b/testing/mozbase/mozversion/mozversion/__init__.py
@@ -0,0 +1,7 @@
+# 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 .errors import *
+from .mozversion import cli, get_version
diff --git a/testing/mozbase/mozversion/mozversion/errors.py b/testing/mozbase/mozversion/mozversion/errors.py
new file mode 100644
index 000000000..756e772d6
--- /dev/null
+++ b/testing/mozbase/mozversion/mozversion/errors.py
@@ -0,0 +1,30 @@
+# 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/.
+
+
+class VersionError(Exception):
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
+
+
+class AppNotFoundError(VersionError):
+ """Exception for the application not found"""
+
+ def __init__(self, message):
+ VersionError.__init__(self, message)
+
+
+class LocalAppNotFoundError(AppNotFoundError):
+ """Exception for local application not found"""
+
+ def __init__(self, path):
+ AppNotFoundError.__init__(self, 'Application not found at: %s' % path)
+
+
+class RemoteAppNotFoundError(AppNotFoundError):
+ """Exception for remote application not found"""
+
+ def __init__(self, message):
+ AppNotFoundError.__init__(self, message)
diff --git a/testing/mozbase/mozversion/mozversion/mozversion.py b/testing/mozbase/mozversion/mozversion/mozversion.py
new file mode 100644
index 000000000..5dfcd306a
--- /dev/null
+++ b/testing/mozbase/mozversion/mozversion/mozversion.py
@@ -0,0 +1,340 @@
+# 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 argparse
+import ConfigParser
+from StringIO import StringIO
+import os
+import re
+import sys
+import tempfile
+import xml.dom.minidom
+import zipfile
+
+import mozfile
+import mozlog
+
+import errors
+
+
+INI_DATA_MAPPING = (('application', 'App'), ('platform', 'Build'))
+
+
+class Version(object):
+
+ def __init__(self):
+ self._info = {}
+ self._logger = mozlog.get_default_logger(component='mozversion')
+ if not self._logger:
+ self._logger = mozlog.unstructured.getLogger('mozversion')
+
+ def get_gecko_info(self, path):
+ for type, section in INI_DATA_MAPPING:
+ config_file = os.path.join(path, "%s.ini" % type)
+ if os.path.exists(config_file):
+ self._parse_ini_file(open(config_file), type, section)
+ else:
+ self._logger.warning('Unable to find %s' % config_file)
+
+ def _parse_ini_file(self, fp, type, section):
+ config = ConfigParser.RawConfigParser()
+ config.readfp(fp)
+ name_map = {'codename': 'display_name',
+ 'milestone': 'version',
+ 'sourcerepository': 'repository',
+ 'sourcestamp': 'changeset'}
+ for key, value in config.items(section):
+ name = name_map.get(key, key).lower()
+ self._info['%s_%s' % (type, name)] = config.has_option(
+ section, key) and config.get(section, key) or None
+
+ if not self._info.get('application_display_name'):
+ self._info['application_display_name'] = \
+ self._info.get('application_name')
+
+
+class LocalFennecVersion(Version):
+
+ def __init__(self, path, **kwargs):
+ Version.__init__(self, **kwargs)
+ self.get_gecko_info(path)
+
+ def get_gecko_info(self, path):
+ archive = zipfile.ZipFile(path, 'r')
+ archive_list = archive.namelist()
+ for type, section in INI_DATA_MAPPING:
+ filename = "%s.ini" % type
+ if filename in archive_list:
+ self._parse_ini_file(archive.open(filename), type,
+ section)
+ else:
+ self._logger.warning('Unable to find %s' % filename)
+
+ if "package-name.txt" in archive_list:
+ self._info["package_name"] = \
+ archive.open("package-name.txt").readlines()[0].strip()
+
+
+class LocalVersion(Version):
+
+ def __init__(self, binary, **kwargs):
+ Version.__init__(self, **kwargs)
+
+ if binary:
+ # on Windows, the binary may be specified with or without the
+ # .exe extension
+ if not os.path.exists(binary) and not os.path.exists(binary +
+ '.exe'):
+ raise IOError('Binary path does not exist: %s' % binary)
+ path = os.path.dirname(os.path.realpath(binary))
+ else:
+ path = os.getcwd()
+
+ if not self.check_location(path):
+ if sys.platform == 'darwin':
+ resources_path = os.path.join(os.path.dirname(path),
+ 'Resources')
+ if self.check_location(resources_path):
+ path = resources_path
+ else:
+ raise errors.LocalAppNotFoundError(path)
+ else:
+ raise errors.LocalAppNotFoundError(path)
+
+ self.get_gecko_info(path)
+
+ def check_location(self, path):
+ return (os.path.exists(os.path.join(path, 'application.ini'))
+ and os.path.exists(os.path.join(path, 'platform.ini')))
+
+
+class B2GVersion(Version):
+
+ def __init__(self, sources=None, **kwargs):
+ Version.__init__(self, **kwargs)
+
+ sources = sources or \
+ os.path.exists(os.path.join(os.getcwd(), 'sources.xml')) and \
+ os.path.join(os.getcwd(), 'sources.xml')
+
+ if sources and os.path.exists(sources):
+ sources_xml = xml.dom.minidom.parse(sources)
+ for element in sources_xml.getElementsByTagName('project'):
+ path = element.getAttribute('path')
+ changeset = element.getAttribute('revision')
+ if path in ['gaia', 'gecko', 'build']:
+ if path == 'gaia' and self._info.get('gaia_changeset'):
+ break
+ self._info['_'.join([path, 'changeset'])] = changeset
+
+ def get_gaia_info(self, app_zip):
+ tempdir = tempfile.mkdtemp()
+ try:
+ gaia_commit = os.path.join(tempdir, 'gaia_commit.txt')
+ try:
+ zip_file = zipfile.ZipFile(app_zip.name)
+ with open(gaia_commit, 'w') as f:
+ f.write(zip_file.read('resources/gaia_commit.txt'))
+ except zipfile.BadZipfile:
+ self._logger.info('Unable to unzip application.zip, falling '
+ 'back to system unzip')
+ from subprocess import call
+ call(['unzip', '-j', app_zip.name, 'resources/gaia_commit.txt',
+ '-d', tempdir])
+
+ with open(gaia_commit) as f:
+ changeset, date = f.read().splitlines()
+ self._info['gaia_changeset'] = re.match(
+ '^\w{40}$', changeset) and changeset or None
+ self._info['gaia_date'] = date
+ except KeyError:
+ self._logger.warning(
+ 'Unable to find resources/gaia_commit.txt in '
+ 'application.zip')
+ finally:
+ mozfile.remove(tempdir)
+
+
+class LocalB2GVersion(B2GVersion):
+
+ def __init__(self, binary, sources=None, **kwargs):
+ B2GVersion.__init__(self, sources, **kwargs)
+
+ if binary:
+ if not os.path.exists(binary):
+ raise IOError('Binary path does not exist: %s' % binary)
+ path = os.path.dirname(binary)
+ else:
+ if os.path.exists(os.path.join(os.getcwd(), 'application.ini')):
+ path = os.getcwd()
+
+ self.get_gecko_info(path)
+
+ zip_path = os.path.join(
+ path, 'gaia', 'profile', 'webapps',
+ 'settings.gaiamobile.org', 'application.zip')
+ if os.path.exists(zip_path):
+ with open(zip_path, 'rb') as zip_file:
+ self.get_gaia_info(zip_file)
+ else:
+ self._logger.warning('Error pulling gaia file')
+
+
+class RemoteB2GVersion(B2GVersion):
+
+ def __init__(self, sources=None, dm_type='adb', host=None,
+ device_serial=None, adb_host=None, adb_port=None,
+ **kwargs):
+ B2GVersion.__init__(self, sources, **kwargs)
+
+ try:
+ import mozdevice
+ except ImportError:
+ self._logger.critical("mozdevice is required to get the version"
+ " of a remote device")
+ raise
+
+ if dm_type == 'adb':
+ dm = mozdevice.DeviceManagerADB(deviceSerial=device_serial,
+ serverHost=adb_host,
+ serverPort=adb_port)
+ elif dm_type == 'sut':
+ if not host:
+ raise errors.RemoteAppNotFoundError(
+ 'A host for SUT must be supplied.')
+ dm = mozdevice.DeviceManagerSUT(host=host)
+ else:
+ raise errors.RemoteAppNotFoundError(
+ 'Unknown device manager type: %s' % dm_type)
+
+ if not sources:
+ path = 'system/sources.xml'
+ if dm.fileExists(path):
+ sources = StringIO(dm.pullFile(path))
+ else:
+ self._logger.info('Unable to find %s' % path)
+
+ tempdir = tempfile.mkdtemp()
+ for ini in ('application', 'platform'):
+ with open(os.path.join(tempdir, '%s.ini' % ini), 'w') as f:
+ f.write(dm.pullFile('/system/b2g/%s.ini' % ini))
+ f.flush()
+ self.get_gecko_info(tempdir)
+ mozfile.remove(tempdir)
+
+ for path in ['/system/b2g', '/data/local']:
+ path += '/webapps/settings.gaiamobile.org/application.zip'
+ if dm.fileExists(path):
+ with tempfile.NamedTemporaryFile() as f:
+ dm.getFile(path, f.name)
+ self.get_gaia_info(f)
+ break
+ else:
+ self._logger.warning('Error pulling gaia file')
+
+ build_props = dm.pullFile('/system/build.prop')
+ desired_props = {
+ 'ro.build.version.incremental': 'device_firmware_version_incremental',
+ 'ro.build.version.release': 'device_firmware_version_release',
+ 'ro.build.date.utc': 'device_firmware_date',
+ 'ro.product.device': 'device_id'}
+ for line in build_props.split('\n'):
+ if not line.strip().startswith('#') and '=' in line:
+ key, value = [s.strip() for s in line.split('=', 1)]
+ if key in desired_props.keys():
+ self._info[desired_props[key]] = value
+
+ if self._info.get('device_id', '').lower() == 'flame':
+ for prop in ['ro.boot.bootloader', 't2m.sw.version']:
+ value = dm.shellCheckOutput(['getprop', prop])
+ if value:
+ self._info['device_firmware_version_base'] = value
+ break
+
+
+def get_version(binary=None, sources=None, dm_type=None, host=None,
+ device_serial=None, adb_host=None, adb_port=None):
+ """
+ Returns the application version information as a dict. You can specify
+ a path to the binary of the application or an Android APK file (to get
+ version information for Firefox for Android). If this is omitted then the
+ current directory is checked for the existance of an application.ini
+ file. If not found and that the binary path was not specified, then it is
+ assumed the target application is a remote Firefox OS instance.
+
+ :param binary: Path to the binary for the application or Android APK file
+ :param sources: Path to the sources.xml file (Firefox OS)
+ :param dm_type: Device manager type. Must be 'adb' or 'sut' (Firefox OS)
+ :param host: Host address of remote Firefox OS instance (SUT)
+ :param device_serial: Serial identifier of Firefox OS device (ADB)
+ :param adb_host: Host address of ADB server
+ :param adb_port: Port of ADB server
+ """
+ try:
+ if binary and zipfile.is_zipfile(binary) and 'AndroidManifest.xml' in \
+ zipfile.ZipFile(binary, 'r').namelist():
+ version = LocalFennecVersion(binary)
+ else:
+ version = LocalVersion(binary)
+ if version._info.get('application_name') == 'B2G':
+ version = LocalB2GVersion(binary, sources=sources)
+ except errors.LocalAppNotFoundError:
+ if binary:
+ # we had a binary argument, do not search for remote B2G
+ raise
+ version = RemoteB2GVersion(sources=sources,
+ dm_type=dm_type,
+ host=host,
+ adb_host=adb_host,
+ adb_port=adb_port,
+ device_serial=device_serial)
+
+ for (key, value) in sorted(version._info.items()):
+ if value:
+ version._logger.info('%s: %s' % (key, value))
+
+ return version._info
+
+
+def cli(args=sys.argv[1:]):
+ parser = argparse.ArgumentParser(
+ description='Display version information for Mozilla applications')
+ parser.add_argument(
+ '--binary',
+ help='path to application binary or apk')
+ fxos = parser.add_argument_group('Firefox OS')
+ fxos.add_argument(
+ '--sources',
+ help='path to sources.xml')
+ fxos.add_argument(
+ '--device',
+ help='serial identifier of device to target')
+ fxos.add_argument(
+ '--adb-host',
+ help='host running adb')
+ fxos.add_argument(
+ '--adb-port',
+ help='port running adb')
+ mozlog.commandline.add_logging_group(
+ parser,
+ include_formatters=mozlog.commandline.TEXT_FORMATTERS
+ )
+
+ args = parser.parse_args()
+ dm_type = os.environ.get('DM_TRANS', 'adb')
+ host = os.environ.get('TEST_DEVICE')
+
+ mozlog.commandline.setup_logging(
+ 'mozversion', args, {'mach': sys.stdout})
+
+ get_version(binary=args.binary,
+ sources=args.sources,
+ dm_type=dm_type,
+ host=host,
+ device_serial=args.device,
+ adb_host=args.adb_host,
+ adb_port=args.adb_port)
+
+if __name__ == '__main__':
+ cli()
diff --git a/testing/mozbase/mozversion/setup.py b/testing/mozbase/mozversion/setup.py
new file mode 100644
index 000000000..09b027925
--- /dev/null
+++ b/testing/mozbase/mozversion/setup.py
@@ -0,0 +1,29 @@
+# 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 = '1.4'
+
+
+setup(name='mozversion',
+ version=PACKAGE_VERSION,
+ description='Library to get version information for applications',
+ long_description='See http://mozbase.readthedocs.org',
+ 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=['mozversion'],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=['mozfile >= 1.0', 'mozlog >= 3.0'],
+ extras_require={'device': ['mozdevice >= 0.44']},
+ entry_points="""
+ # -*- Entry points: -*-
+ [console_scripts]
+ mozversion = mozversion:cli
+ """)
diff --git a/testing/mozbase/mozversion/tests/manifest.ini b/testing/mozbase/mozversion/tests/manifest.ini
new file mode 100644
index 000000000..3c034ea78
--- /dev/null
+++ b/testing/mozbase/mozversion/tests/manifest.ini
@@ -0,0 +1,4 @@
+[test_binary.py]
+[test_sources.py]
+[test_b2g.py]
+[test_apk.py] \ No newline at end of file
diff --git a/testing/mozbase/mozversion/tests/test_apk.py b/testing/mozbase/mozversion/tests/test_apk.py
new file mode 100644
index 000000000..37fcb0bcf
--- /dev/null
+++ b/testing/mozbase/mozversion/tests/test_apk.py
@@ -0,0 +1,43 @@
+#!/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 mozfile
+import unittest
+import zipfile
+from mozversion import get_version
+
+
+class ApkTest(unittest.TestCase):
+ """test getting version information from an android .apk"""
+
+ application_changeset = 'a' * 40
+ platform_changeset = 'b' * 40
+
+ def create_apk_zipfiles(self, zfile):
+ zfile.writestr('application.ini',
+ """[App]\nSourceStamp=%s\n""" % self.application_changeset)
+ zfile.writestr('platform.ini',
+ """[Build]\nSourceStamp=%s\n""" % self.platform_changeset)
+ zfile.writestr('AndroidManifest.xml', '')
+
+ def test_basic(self):
+ with mozfile.NamedTemporaryFile() as f:
+ with zipfile.ZipFile(f.name, 'w') as z:
+ self.create_apk_zipfiles(z)
+ v = get_version(f.name)
+ self.assertEqual(v.get('application_changeset'), self.application_changeset)
+ self.assertEqual(v.get('platform_changeset'), self.platform_changeset)
+
+ def test_with_package_name(self):
+ with mozfile.NamedTemporaryFile() as f:
+ with zipfile.ZipFile(f.name, 'w') as z:
+ self.create_apk_zipfiles(z)
+ z.writestr('package-name.txt', "org.mozilla.fennec")
+ v = get_version(f.name)
+ self.assertEqual(v.get('package_name'), "org.mozilla.fennec")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/testing/mozbase/mozversion/tests/test_b2g.py b/testing/mozbase/mozversion/tests/test_b2g.py
new file mode 100644
index 000000000..09e0eb09e
--- /dev/null
+++ b/testing/mozbase/mozversion/tests/test_b2g.py
@@ -0,0 +1,75 @@
+#!/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 os
+import tempfile
+import unittest
+import zipfile
+
+import mozfile
+from mozversion import get_version, errors
+
+
+class SourcesTest(unittest.TestCase):
+ """test getting version information from a sources xml"""
+
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+
+ self.binary = os.path.join(self.tempdir, 'binary')
+ with open(self.binary, 'w') as f:
+ f.write('foobar')
+
+ with open(os.path.join(self.tempdir, 'application.ini'), 'w') as f:
+ f.writelines("""[App]\nName = B2G\n""")
+
+ with open(os.path.join(self.tempdir, 'platform.ini'), 'w') as f:
+ f.write('[Build]\nBuildID = PlatformBuildID\n')
+
+ def tearDown(self):
+ mozfile.remove(self.tempdir)
+
+ def _create_zip(self, revision=None, date=None):
+ zip_path = os.path.join(
+ self.tempdir, 'gaia', 'profile', 'webapps',
+ 'settings.gaiamobile.org', 'application.zip')
+ os.makedirs(os.path.dirname(zip_path))
+ app_zip = zipfile.ZipFile(zip_path, 'w')
+ if revision or date:
+ app_zip.writestr('resources/gaia_commit.txt',
+ '%s\n%s' % (revision, date))
+ app_zip.close()
+
+ def test_gaia_commit(self):
+ revision, date = ('a' * 40, 'date')
+ self._create_zip(revision, date)
+ v = get_version(self.binary)
+ self.assertEqual(v.get('gaia_changeset'), revision)
+ self.assertEqual(v.get('gaia_date'), date)
+
+ def test_invalid_gaia_commit(self):
+ revision, date = ('a' * 41, 'date')
+ self._create_zip(revision, date)
+ v = get_version(self.binary)
+ self.assertIsNone(v.get('gaia_changeset'))
+ self.assertEqual(v.get('gaia_date'), date)
+
+ def test_missing_zip_file(self):
+ v = get_version(self.binary)
+ self.assertIsNone(v.get('gaia_changeset'))
+ self.assertIsNone(v.get('gaia_date'))
+
+ def test_missing_gaia_commit(self):
+ self._create_zip()
+ v = get_version(self.binary)
+ self.assertIsNone(v.get('gaia_changeset'))
+ self.assertIsNone(v.get('gaia_date'))
+
+ def test_b2g_fallback_when_no_binary(self):
+ self.assertRaises(errors.RemoteAppNotFoundError, get_version)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/testing/mozbase/mozversion/tests/test_binary.py b/testing/mozbase/mozversion/tests/test_binary.py
new file mode 100644
index 000000000..54665974f
--- /dev/null
+++ b/testing/mozbase/mozversion/tests/test_binary.py
@@ -0,0 +1,177 @@
+#!/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 os
+import sys
+import tempfile
+import shutil
+import unittest
+
+import mozfile
+
+from mozversion import errors, get_version
+
+
+class BinaryTest(unittest.TestCase):
+ """test getting application version information from a binary path"""
+
+ application_ini = """[App]
+ID = AppID
+Name = AppName
+CodeName = AppCodeName
+Version = AppVersion
+BuildID = AppBuildID
+SourceRepository = AppSourceRepo
+SourceStamp = AppSourceStamp
+Vendor = AppVendor
+"""
+ platform_ini = """[Build]
+BuildID = PlatformBuildID
+Milestone = PlatformMilestone
+SourceStamp = PlatformSourceStamp
+SourceRepository = PlatformSourceRepo
+"""
+
+ def setUp(self):
+ self.cwd = os.getcwd()
+ self.tempdir = tempfile.mkdtemp()
+
+ self.binary = os.path.join(self.tempdir, 'binary')
+ with open(self.binary, 'w') as f:
+ f.write('foobar')
+
+ def tearDown(self):
+ os.chdir(self.cwd)
+ mozfile.remove(self.tempdir)
+
+ @unittest.skipIf(not os.environ.get('BROWSER_PATH'),
+ 'No binary has been specified.')
+ def test_real_binary(self):
+ v = get_version(os.environ.get('BROWSER_PATH'))
+ self.assertTrue(isinstance(v, dict))
+
+ def test_binary(self):
+ self._write_ini_files()
+
+ self._check_version(get_version(self.binary))
+
+ @unittest.skipIf(not hasattr(os, 'symlink'),
+ 'os.symlink not supported on this platform')
+ def test_symlinked_binary(self):
+ self._write_ini_files()
+
+ # create a symlink of the binary in another directory and check
+ # version against this symlink
+ tempdir = tempfile.mkdtemp()
+ try:
+ browser_link = os.path.join(tempdir,
+ os.path.basename(self.binary))
+ os.symlink(self.binary, browser_link)
+
+ self._check_version(get_version(browser_link))
+ finally:
+ mozfile.remove(tempdir)
+
+ def test_binary_in_current_path(self):
+ self._write_ini_files()
+
+ os.chdir(self.tempdir)
+ self._check_version(get_version())
+
+ def test_with_ini_files_on_osx(self):
+ self._write_ini_files()
+
+ platform = sys.platform
+ sys.platform = 'darwin'
+ try:
+ # get_version is working with ini files next to the binary
+ self._check_version(get_version(binary=self.binary))
+
+ # or if they are in the Resources dir
+ # in this case the binary must be in a Contents dir, next
+ # to the Resources dir
+ contents_dir = os.path.join(self.tempdir, 'Contents')
+ os.mkdir(contents_dir)
+ moved_binary = os.path.join(contents_dir,
+ os.path.basename(self.binary))
+ shutil.move(self.binary, moved_binary)
+
+ resources_dir = os.path.join(self.tempdir, 'Resources')
+ os.mkdir(resources_dir)
+ for ini_file in ('application.ini', 'platform.ini'):
+ shutil.move(os.path.join(self.tempdir, ini_file), resources_dir)
+
+ self._check_version(get_version(binary=moved_binary))
+ finally:
+ sys.platform = platform
+
+ def test_invalid_binary_path(self):
+ self.assertRaises(IOError, get_version,
+ os.path.join(self.tempdir, 'invalid'))
+
+ def test_without_ini_files(self):
+ """With missing ini files an exception should be thrown"""
+ self.assertRaises(errors.AppNotFoundError, get_version,
+ self.binary)
+
+ def test_without_platform_ini_file(self):
+ """With a missing platform.ini file an exception should be thrown"""
+ self._write_ini_files(platform=False)
+ self.assertRaises(errors.AppNotFoundError, get_version,
+ self.binary)
+
+ def test_without_application_ini_file(self):
+ """With a missing application.ini file an exception should be thrown"""
+ self._write_ini_files(application=False)
+ self.assertRaises(errors.AppNotFoundError, get_version,
+ self.binary)
+
+ def test_with_exe(self):
+ """Test that we can resolve .exe files"""
+ self._write_ini_files()
+
+ exe_name_unprefixed = self.binary + '1'
+ exe_name = exe_name_unprefixed + '.exe'
+ with open(exe_name, 'w') as f:
+ f.write('foobar')
+ self._check_version(get_version(exe_name_unprefixed))
+
+ def test_not_found_with_binary_specified(self):
+ self.assertRaises(errors.LocalAppNotFoundError, get_version, self.binary)
+
+ def _write_ini_files(self, application=True, platform=True):
+ if application:
+ with open(os.path.join(self.tempdir, 'application.ini'), 'w') as f:
+ f.writelines(self.application_ini)
+ if platform:
+ with open(os.path.join(self.tempdir, 'platform.ini'), 'w') as f:
+ f.writelines(self.platform_ini)
+
+ def _check_version(self, version):
+ self.assertEqual(version.get('application_id'), 'AppID')
+ self.assertEqual(version.get('application_name'), 'AppName')
+ self.assertEqual(
+ version.get('application_display_name'), 'AppCodeName')
+ self.assertEqual(version.get('application_version'), 'AppVersion')
+ self.assertEqual(version.get('application_buildid'), 'AppBuildID')
+ self.assertEqual(
+ version.get('application_repository'), 'AppSourceRepo')
+ self.assertEqual(
+ version.get('application_changeset'), 'AppSourceStamp')
+ self.assertEqual(version.get('application_vendor'), 'AppVendor')
+ self.assertIsNone(version.get('platform_name'))
+ self.assertEqual(version.get('platform_buildid'), 'PlatformBuildID')
+ self.assertEqual(
+ version.get('platform_repository'), 'PlatformSourceRepo')
+ self.assertEqual(
+ version.get('platform_changeset'), 'PlatformSourceStamp')
+ self.assertIsNone(version.get('invalid_key'))
+ self.assertEqual(
+ version.get('platform_version'), 'PlatformMilestone')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/testing/mozbase/mozversion/tests/test_sources.py b/testing/mozbase/mozversion/tests/test_sources.py
new file mode 100644
index 000000000..6c663edd6
--- /dev/null
+++ b/testing/mozbase/mozversion/tests/test_sources.py
@@ -0,0 +1,85 @@
+#!/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 os
+import tempfile
+import unittest
+
+import mozfile
+
+from mozversion import errors, get_version
+
+
+class SourcesTest(unittest.TestCase):
+ """test getting version information from a sources xml"""
+
+ application_ini = """[App]\nName = B2G\n"""
+ platform_ini = """[Build]
+BuildID = PlatformBuildID
+SourceStamp = PlatformSourceStamp
+SourceRepository = PlatformSourceRepo
+"""
+ sources_xml = """<?xml version="1.0" ?><manifest>
+ <project path="build" revision="build_revision" />
+ <project path="gaia" revision="gaia_revision" />
+ <project path="gecko" revision="gecko_revision" />
+</manifest>
+"""
+
+ def setUp(self):
+ self.cwd = os.getcwd()
+ self.tempdir = tempfile.mkdtemp()
+
+ self.binary = os.path.join(self.tempdir, 'binary')
+ with open(self.binary, 'w') as f:
+ f.write('foobar')
+
+ def tearDown(self):
+ os.chdir(self.cwd)
+ mozfile.remove(self.tempdir)
+
+ def _write_conf_files(self, sources=True):
+ with open(os.path.join(self.tempdir, 'application.ini'), 'w') as f:
+ f.writelines(self.application_ini)
+ with open(os.path.join(self.tempdir, 'platform.ini'), 'w') as f:
+ f.writelines(self.platform_ini)
+ if sources:
+ with open(os.path.join(self.tempdir, 'sources.xml'), 'w') as f:
+ f.writelines(self.sources_xml)
+
+ def test_sources(self):
+ self._write_conf_files()
+
+ os.chdir(self.tempdir)
+ self._check_version(get_version(sources=os.path.join(self.tempdir,
+ 'sources.xml')))
+
+ def test_sources_in_current_directory(self):
+ self._write_conf_files()
+
+ os.chdir(self.tempdir)
+ self._check_version(get_version())
+
+ def test_invalid_sources_path(self):
+ """An invalid source path should cause an exception"""
+ self.assertRaises(errors.AppNotFoundError, get_version,
+ self.binary, os.path.join(self.tempdir, 'invalid'))
+
+ def test_without_sources_file(self):
+ """With a missing sources file no exception should be thrown"""
+ self._write_conf_files(sources=False)
+
+ get_version(self.binary)
+
+ def _check_version(self, version):
+ self.assertEqual(version.get('build_changeset'), 'build_revision')
+ self.assertEqual(version.get('gaia_changeset'), 'gaia_revision')
+ self.assertEqual(version.get('gecko_changeset'), 'gecko_revision')
+ self.assertIsNone(version.get('invalid_key'))
+
+
+if __name__ == '__main__':
+ unittest.main()