summaryrefslogtreecommitdiff
path: root/libre/iceweasel/firefox-70.0-add-distro.patch
diff options
context:
space:
mode:
Diffstat (limited to 'libre/iceweasel/firefox-70.0-add-distro.patch')
-rw-r--r--libre/iceweasel/firefox-70.0-add-distro.patch6965
1 files changed, 6965 insertions, 0 deletions
diff --git a/libre/iceweasel/firefox-70.0-add-distro.patch b/libre/iceweasel/firefox-70.0-add-distro.patch
new file mode 100644
index 000000000..8db8ce5dc
--- /dev/null
+++ b/libre/iceweasel/firefox-70.0-add-distro.patch
@@ -0,0 +1,6965 @@
+
+# HG changeset patch
+# User Edwin Takahashi <egao@mozilla.com>
+# Date 1572468170 0
+# Node ID d2d9fe01fc33af4538940e833a6696d973ebea74
+# Parent eddb9fcaaa4bd4fdb4e32024f92f969abfe92f58
+Bug 1212502 - Switch mozinfo to using the 'distro' package to get linux distribution info r=ahal,KWierso
+
+Differential Revision: https://phabricator.services.mozilla.com/D49366
+
+diff --git a/build/virtualenv_packages.txt b/build/virtualenv_packages.txt
+--- a/build/virtualenv_packages.txt
++++ b/build/virtualenv_packages.txt
+@@ -10,16 +10,17 @@ mozilla.pth:third_party/python/atomicwri
+ mozilla.pth:third_party/python/attrs/src
+ python2:mozilla.pth:third_party/python/backports
+ mozilla.pth:third_party/python/biplist
+ mozilla.pth:third_party/python/blessings
+ mozilla.pth:third_party/python/Click
+ mozilla.pth:third_party/python/compare-locales
+ mozilla.pth:third_party/python/configobj
+ mozilla.pth:third_party/python/cram
++mozilla.pth:third_party/python/distro
+ mozilla.pth:third_party/python/dlmanager
+ mozilla.pth:third_party/python/enum34
+ mozilla.pth:third_party/python/fluent
+ mozilla.pth:third_party/python/funcsigs
+ python2:mozilla.pth:third_party/python/futures
+ mozilla.pth:third_party/python/mohawk
+ mozilla.pth:third_party/python/more-itertools
+ mozilla.pth:third_party/python/mozilla-version
+diff --git a/python/mozbuild/mozbuild/action/test_archive.py b/python/mozbuild/mozbuild/action/test_archive.py
+--- a/python/mozbuild/mozbuild/action/test_archive.py
++++ b/python/mozbuild/mozbuild/action/test_archive.py
+@@ -245,16 +245,22 @@ ARCHIVE_FILES = {
+ },
+ {
+ 'source': buildconfig.topsrcdir,
+ 'base': 'third_party/python/six',
+ 'pattern': '**',
+ 'dest': 'tools/six',
+ },
+ {
++ 'source': buildconfig.topsrcdir,
++ 'base': 'third_party/python/distro',
++ 'pattern': '**',
++ 'dest': 'tools/distro',
++ },
++ {
+ 'source': buildconfig.topobjdir,
+ 'base': '',
+ 'pattern': 'mozinfo.json',
+ },
+ {
+ 'source': buildconfig.topobjdir,
+ 'base': 'dist/bin',
+ 'patterns': [
+@@ -431,16 +437,22 @@ ARCHIVE_FILES = {
+ 'dest': 'mozharness',
+ },
+ {
+ 'source': buildconfig.topsrcdir,
+ 'base': 'third_party/python/six',
+ 'pattern': 'six.py',
+ 'dest': 'mozharness',
+ },
++ {
++ 'source': buildconfig.topsrcdir,
++ 'base': 'third_party/python/distro',
++ 'pattern': 'distro.py',
++ 'dest': 'mozharness',
++ },
+ ],
+ 'reftest': [
+ {
+ 'source': buildconfig.topobjdir,
+ 'base': '_tests',
+ 'pattern': 'reftest/**',
+ },
+ {
+diff --git a/testing/mozbase/mozinfo/mozinfo/mozinfo.py b/testing/mozbase/mozinfo/mozinfo/mozinfo.py
+--- a/testing/mozbase/mozinfo/mozinfo/mozinfo.py
++++ b/testing/mozbase/mozinfo/mozinfo/mozinfo.py
+@@ -9,16 +9,17 @@
+ # information and having behaviour depend on it
+
+ from __future__ import absolute_import, print_function
+
+ import os
+ import platform
+ import re
+ import sys
++
+ from .string_version import StringVersion
+ from ctypes.util import find_library
+
+ # keep a copy of the os module since updating globals overrides this
+ _os = os
+
+
+ class unknown(object):
+@@ -93,35 +94,45 @@ if system in ["Microsoft", "Windows"]:
+ version = "%d.%d.%d" % (major, minor, build_number)
+
+ os_version = "%d.%d" % (major, minor)
+ elif system.startswith(('MINGW', 'MSYS_NT')):
+ # 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()
++ # Only attempt to import distro for Linux.
++ # https://github.com/nir0s/distro/issues/177
++ try:
++ import distro
++ except ImportError:
++ pass
++ # First use distro package, then fall back to platform.
++ # This will only until Mozilla upgrades python to 3.8.
++ if hasattr(distro, "linux_distribution"):
++ (distribution, os_version, codename) = distro.linux_distribution()
++ elif hasattr(platform, "linux_distribution"):
++ (distribution, os_version, codename) = platform.linux_distribution()
+ else:
+- (distro, os_version, codename) = platform.dist()
++ (distribution, os_version, codename) = platform.dist()
+ if not processor:
+ processor = machine
+- version = "%s %s" % (distro, os_version)
++ version = "%s %s" % (distribution, 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'
++ if not distribution and not os_version and not codename:
++ distribution = 'lfs'
+ version = release
+ os_version = release
+
+ info['os'] = 'linux'
+- info['linux_distro'] = distro
++ info['linux_distro'] = distribution
+ 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])
+diff --git a/testing/mozbase/mozinfo/setup.py b/testing/mozbase/mozinfo/setup.py
+--- a/testing/mozbase/mozinfo/setup.py
++++ b/testing/mozbase/mozinfo/setup.py
+@@ -4,17 +4,20 @@
+
+ from __future__ import absolute_import
+
+ from setuptools import setup
+
+ PACKAGE_VERSION = "1.1.0"
+
+ # dependencies
+-deps = ["mozfile >= 0.12"]
++deps = [
++ "distro == 1.4.0",
++ "mozfile >= 0.12",
++]
+
+ setup(
+ name="mozinfo",
+ version=PACKAGE_VERSION,
+ description="Library to get system information for use in Mozilla testing",
+ long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
+ classifiers=[
+ "Programming Language :: Python :: 2.7",
+diff --git a/testing/mozharness/tox.ini b/testing/mozharness/tox.ini
+--- a/testing/mozharness/tox.ini
++++ b/testing/mozharness/tox.ini
+@@ -1,14 +1,15 @@
+ [tox]
+ envlist = py27-hg4.3
+
+ [base]
+ deps =
+ coverage
++ distro
+ nose
+ rednose
+ {toxinidir}/../mozbase/mozlog
+ mozbase = {toxinidir}/../mozbase/
+
+
+ [testenv]
+ basepython = python2.7
+diff --git a/third_party/python/distro/CHANGELOG.md b/third_party/python/distro/CHANGELOG.md
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/CHANGELOG.md
+@@ -0,0 +1,147 @@
++## 1.4.0 (2019.2.4)
++
++BACKWARD COMPATIBILITY:
++* Prefer the VERSION_CODENAME field of os-release to parsing it from VERSION [[#230](https://github.com/nir0s/distro/pull/230)]
++
++BUG FIXES:
++* Return _uname_info from the uname_info() method [[#233](https://github.com/nir0s/distro/pull/233)]
++* Fixed CloudLinux id discovery [[#234](https://github.com/nir0s/distro/pull/234)]
++* Update Oracle matching [[#224](https://github.com/nir0s/distro/pull/224)]
++
++DOCS:
++* Update Fedora package link [[#225](https://github.com/nir0s/distro/pull/225)]
++* Distro is the recommended replacement for platform.linux_distribution [[#220](https://github.com/nir0s/distro/pull/220)]
++
++RELEASE:
++* Use Markdown for long description in setup.py [[#219](https://github.com/nir0s/distro/pull/219)]
++
++Additionally, The Python2.6 branch was fixed and rebased on top of master. It is now passing all tests. Thanks [abadger](https://github.com/abadger)!
++
++## 1.3.0 (2018.05.09)
++
++ENHANCEMENTS:
++* Added support for OpenBSD, FreeBSD, and NetBSD [[#207](https://github.com/nir0s/distro/issues/207)]
++
++TESTS:
++* Add test for Kali Linux Rolling [[#214](https://github.com/nir0s/distro/issues/214)]
++
++DOCS:
++* Update docs with regards to #207 [[#209](https://github.com/nir0s/distro/issues/209)]
++* Add Ansible reference implementation and fix arch-linux link [[#213](https://github.com/nir0s/distro/issues/213)]
++* Add facter reference implementation [[#213](https://github.com/nir0s/distro/issues/213)]
++
++## 1.2.0 (2017.12.24)
++
++BACKWARD COMPATIBILITY:
++* Don't raise ImportError on non-linux platforms [[#202](https://github.com/nir0s/distro/issues/202)]
++
++ENHANCEMENTS:
++* Lazily load the LinuxDistribution data [[#201](https://github.com/nir0s/distro/issues/201)]
++
++BUG FIXES:
++* Stdout of shell should be decoded with sys.getfilesystemencoding() [[#203](https://github.com/nir0s/distro/issues/203)]
++
++TESTS:
++* Explicitly set Python versions on Travis for flake [[#204](https://github.com/nir0s/distro/issues/204)]
++
++
++## 1.1.0 (2017.11.28)
++
++BACKWARD COMPATIBILITY:
++* Drop python3.3 support [[#199](https://github.com/nir0s/distro/issues/199)]
++* Remove Official Python26 support [[#195](https://github.com/nir0s/distro/issues/195)]
++
++TESTS:
++* Add MandrivaLinux test case [[#181](https://github.com/nir0s/distro/issues/181)]
++* Add test cases for CloudLinux 5, 6, and 7 [[#180](https://github.com/nir0s/distro/issues/180)]
++
++RELEASE:
++* Modify MANIFEST to include resources for tests and docs in source tarballs [[97c91a1](97c91a1)]
++
++## 1.0.4 (2017.04.01)
++
++BUG FIXES:
++* Guess common *-release files if /etc not readable [[#175](https://github.com/nir0s/distro/issues/175)]
++
++## 1.0.3 (2017.03.19)
++
++ENHANCEMENTS:
++* Show keys for empty values when running distro from the CLI [[#160](https://github.com/nir0s/distro/issues/160)]
++* Add manual mapping for `redhatenterpriseserver` (previously only redhatenterpriseworkstation was mapped) [[#148](https://github.com/nir0s/distro/issues/148)]
++* Race condition in `_parse_distro_release_file` [[#163](https://github.com/nir0s/distro/issues/163)]
++
++TESTS:
++* Add RHEL5 test case [[#165](https://github.com/nir0s/distro/issues/165)]
++* Add OpenELEC test case [[#166](https://github.com/nir0s/distro/issues/166)]
++* Replace nose with pytest [[#158](https://github.com/nir0s/distro/issues/158)]
++
++RELEASE:
++* Update classifiers
++* Update supported Python versions (with py36)
++
++## 1.0.2 (2017.01.12)
++
++TESTS:
++* Test on py33, py36 and py3 based flake8
++
++RELEASE:
++* Add MANIFEST file (which also includes the LICENSE as part of Issue [[#139](https://github.com/nir0s/distro/issues/139)])
++* Default to releasing using Twine [[#121](https://github.com/nir0s/distro/issues/121)]
++* Add setup.cfg file [[#145](https://github.com/nir0s/distro/issues/145)]
++* Update license in setup.py
++
++## 1.0.1 (2016-11-03)
++
++ENHANCEMENTS:
++* Prettify distro -j's output and add more elaborate docs [[#147](https://github.com/nir0s/distro/issues/147)]
++* Decode output of `lsb_release` as utf-8 [[#144](https://github.com/nir0s/distro/issues/144)]
++* Logger now uses `message %s, string` form to not-evaulate log messages if unnecessary [[#145](https://github.com/nir0s/distro/issues/145)]
++
++TESTS:
++* Increase code-coverage [[#146](https://github.com/nir0s/distro/issues/146)]
++* Fix landscape code-quality warnings [[#145](https://github.com/nir0s/distro/issues/145)]
++
++RELEASE:
++* Add CONTRIBUTING.md
++
++## 1.0.0 (2016-09-25)
++
++BACKWARD COMPATIBILITY:
++* raise exception when importing on non-supported platforms [[#129](https://github.com/nir0s/distro/issues/129)]
++
++ENHANCEMENTS:
++* Use `bytes` invariantly [[#135](https://github.com/nir0s/distro/issues/135)]
++* Some minor code adjustments plus a CLI [[#134](https://github.com/nir0s/distro/issues/134)]
++* Emit stderr if `lsb_release` fails
++
++BUG FIXES:
++* Fix some encoding related issues
++
++TESTS:
++* Add many test cases (e.g. Raspbian 8, CoreOS, Amazon Linux, Scientific Linux, Gentoo, Manjaro)
++* Completely redo the testing framework to make it easier to add tests
++* Test on pypy
++
++RELEASE:
++* Remove six as a dependency
++
++## 0.6.0 (2016-04-21)
++
++This is the first release of `distro`.
++All previous work was done on `ld` and therefore unmentioned here. See the release log in GitHub if you want the entire log.
++
++BACKWARD COMPATIBILITY:
++* No longer a package. constants.py has been removed and distro is now a single module
++
++ENHANCEMENTS:
++* distro.info() now receives best and pretty flags
++* Removed get_ prefix from get_*_release_attr functions
++* Codename is now passed in distro.info()
++
++TESTS:
++* Added Linux Mint test case
++* Now testing on Python 3.4
++
++DOCS:
++* Documentation fixes
++
+diff --git a/third_party/python/distro/CONTRIBUTING.md b/third_party/python/distro/CONTRIBUTING.md
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/CONTRIBUTING.md
+@@ -0,0 +1,54 @@
++# General
++
++* Contributing to distro identification currently doesn't have any specific standards and rather depends on the specific implementation.
++* A 100% coverage is expected for each PR unless explicitly authorized by the reviewer.
++* Please try to maintain maximum code-health (via landscape.io).
++
++# Contributing distro specific tests
++
++Distro's tests are implemented via a standardized framework under `tests/test_distro.py`
++
++For each distribution, tests should be added in the relevant class according to which distribution file(s) exists on it, so, for example, tests should be added under `TestOSRelease` where `/etc/os-release` is available.
++
++The tests must be self-contained, meaning that the release files for the distribution should be maintained in the repository under `tests/resources/distros/distribution_name+distribution_version`.
++
++A tests method would like somewhat like this:
++
++```python
++def test_centos7_os_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS Linux',
++ 'pretty_name': 'CentOS Linux 7 (Core)',
++ 'version': '7',
++ 'pretty_version': '7 (Core)',
++ 'best_version': '7',
++ 'like': 'rhel fedora',
++ 'codename': 'Core'
++ }
++ self._test_outcome(desired_outcome)
++```
++
++The framework will automatically try to pick up the relevant file according to the method's name (`centos7` meaning the folder should be named `centos7` as well) and compare the `desired_outcome` with the parsed files found under the test dir.
++
++The exception to the rule is under the `TestDistroRelease` test class which should look somewhat like this:
++
++```python
++def test_centos5_dist_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS',
++ 'pretty_name': 'CentOS 5.11 (Final)',
++ 'version': '5.11',
++ 'pretty_version': '5.11 (Final)',
++ 'best_version': '5.11',
++ 'codename': 'Final',
++ 'major_version': '5',
++ 'minor_version': '11'
++ }
++ self._test_outcome(desired_outcome, 'centos', '5')
++```
++
++Where the name of the method is not indicative of the lookup folder but rather tha two last arguments in `_test_outcome`.
++
++A test case is mandatory under `TestOverall` for a PR to be complete.
+\ No newline at end of file
+diff --git a/third_party/python/distro/CONTRIBUTORS.md b/third_party/python/distro/CONTRIBUTORS.md
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/CONTRIBUTORS.md
+@@ -0,0 +1,13 @@
++Thanks!
++
++* https://github.com/andy-maier
++* https://github.com/SethMichaelLarson
++* https://github.com/asottile
++* https://github.com/MartijnBraam
++* https://github.com/funkyfuture
++* https://github.com/adamjstewart
++* https://github.com/xavfernandez
++* https://github.com/xsuchy
++* https://github.com/marcoceppi
++* https://github.com/tgamblin
++* https://github.com/sebix
+diff --git a/third_party/python/distro/LICENSE b/third_party/python/distro/LICENSE
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/LICENSE
+@@ -0,0 +1,202 @@
++Apache License
++ Version 2.0, January 2004
++ http://www.apache.org/licenses/
++
++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
++
++ 1. Definitions.
++
++ "License" shall mean the terms and conditions for use, reproduction,
++ and distribution as defined by Sections 1 through 9 of this document.
++
++ "Licensor" shall mean the copyright owner or entity authorized by
++ the copyright owner that is granting the License.
++
++ "Legal Entity" shall mean the union of the acting entity and all
++ other entities that control, are controlled by, or are under common
++ control with that entity. For the purposes of this definition,
++ "control" means (i) the power, direct or indirect, to cause the
++ direction or management of such entity, whether by contract or
++ otherwise, or (ii) ownership of fifty percent (50%) or more of the
++ outstanding shares, or (iii) beneficial ownership of such entity.
++
++ "You" (or "Your") shall mean an individual or Legal Entity
++ exercising permissions granted by this License.
++
++ "Source" form shall mean the preferred form for making modifications,
++ including but not limited to software source code, documentation
++ source, and configuration files.
++
++ "Object" form shall mean any form resulting from mechanical
++ transformation or translation of a Source form, including but
++ not limited to compiled object code, generated documentation,
++ and conversions to other media types.
++
++ "Work" shall mean the work of authorship, whether in Source or
++ Object form, made available under the License, as indicated by a
++ copyright notice that is included in or attached to the work
++ (an example is provided in the Appendix below).
++
++ "Derivative Works" shall mean any work, whether in Source or Object
++ form, that is based on (or derived from) the Work and for which the
++ editorial revisions, annotations, elaborations, or other modifications
++ represent, as a whole, an original work of authorship. For the purposes
++ of this License, Derivative Works shall not include works that remain
++ separable from, or merely link (or bind by name) to the interfaces of,
++ the Work and Derivative Works thereof.
++
++ "Contribution" shall mean any work of authorship, including
++ the original version of the Work and any modifications or additions
++ to that Work or Derivative Works thereof, that is intentionally
++ submitted to Licensor for inclusion in the Work by the copyright owner
++ or by an individual or Legal Entity authorized to submit on behalf of
++ the copyright owner. For the purposes of this definition, "submitted"
++ means any form of electronic, verbal, or written communication sent
++ to the Licensor or its representatives, including but not limited to
++ communication on electronic mailing lists, source code control systems,
++ and issue tracking systems that are managed by, or on behalf of, the
++ Licensor for the purpose of discussing and improving the Work, but
++ excluding communication that is conspicuously marked or otherwise
++ designated in writing by the copyright owner as "Not a Contribution."
++
++ "Contributor" shall mean Licensor and any individual or Legal Entity
++ on behalf of whom a Contribution has been received by Licensor and
++ subsequently incorporated within the Work.
++
++ 2. Grant of Copyright License. Subject to the terms and conditions of
++ this License, each Contributor hereby grants to You a perpetual,
++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
++ copyright license to reproduce, prepare Derivative Works of,
++ publicly display, publicly perform, sublicense, and distribute the
++ Work and such Derivative Works in Source or Object form.
++
++ 3. Grant of Patent License. Subject to the terms and conditions of
++ this License, each Contributor hereby grants to You a perpetual,
++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
++ (except as stated in this section) patent license to make, have made,
++ use, offer to sell, sell, import, and otherwise transfer the Work,
++ where such license applies only to those patent claims licensable
++ by such Contributor that are necessarily infringed by their
++ Contribution(s) alone or by combination of their Contribution(s)
++ with the Work to which such Contribution(s) was submitted. If You
++ institute patent litigation against any entity (including a
++ cross-claim or counterclaim in a lawsuit) alleging that the Work
++ or a Contribution incorporated within the Work constitutes direct
++ or contributory patent infringement, then any patent licenses
++ granted to You under this License for that Work shall terminate
++ as of the date such litigation is filed.
++
++ 4. Redistribution. You may reproduce and distribute copies of the
++ Work or Derivative Works thereof in any medium, with or without
++ modifications, and in Source or Object form, provided that You
++ meet the following conditions:
++
++ (a) You must give any other recipients of the Work or
++ Derivative Works a copy of this License; and
++
++ (b) You must cause any modified files to carry prominent notices
++ stating that You changed the files; and
++
++ (c) You must retain, in the Source form of any Derivative Works
++ that You distribute, all copyright, patent, trademark, and
++ attribution notices from the Source form of the Work,
++ excluding those notices that do not pertain to any part of
++ the Derivative Works; and
++
++ (d) If the Work includes a "NOTICE" text file as part of its
++ distribution, then any Derivative Works that You distribute must
++ include a readable copy of the attribution notices contained
++ within such NOTICE file, excluding those notices that do not
++ pertain to any part of the Derivative Works, in at least one
++ of the following places: within a NOTICE text file distributed
++ as part of the Derivative Works; within the Source form or
++ documentation, if provided along with the Derivative Works; or,
++ within a display generated by the Derivative Works, if and
++ wherever such third-party notices normally appear. The contents
++ of the NOTICE file are for informational purposes only and
++ do not modify the License. You may add Your own attribution
++ notices within Derivative Works that You distribute, alongside
++ or as an addendum to the NOTICE text from the Work, provided
++ that such additional attribution notices cannot be construed
++ as modifying the License.
++
++ You may add Your own copyright statement to Your modifications and
++ may provide additional or different license terms and conditions
++ for use, reproduction, or distribution of Your modifications, or
++ for any such Derivative Works as a whole, provided Your use,
++ reproduction, and distribution of the Work otherwise complies with
++ the conditions stated in this License.
++
++ 5. Submission of Contributions. Unless You explicitly state otherwise,
++ any Contribution intentionally submitted for inclusion in the Work
++ by You to the Licensor shall be under the terms and conditions of
++ this License, without any additional terms or conditions.
++ Notwithstanding the above, nothing herein shall supersede or modify
++ the terms of any separate license agreement you may have executed
++ with Licensor regarding such Contributions.
++
++ 6. Trademarks. This License does not grant permission to use the trade
++ names, trademarks, service marks, or product names of the Licensor,
++ except as required for reasonable and customary use in describing the
++ origin of the Work and reproducing the content of the NOTICE file.
++
++ 7. Disclaimer of Warranty. Unless required by applicable law or
++ agreed to in writing, Licensor provides the Work (and each
++ Contributor provides its Contributions) on an "AS IS" BASIS,
++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
++ implied, including, without limitation, any warranties or conditions
++ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
++ PARTICULAR PURPOSE. You are solely responsible for determining the
++ appropriateness of using or redistributing the Work and assume any
++ risks associated with Your exercise of permissions under this License.
++
++ 8. Limitation of Liability. In no event and under no legal theory,
++ whether in tort (including negligence), contract, or otherwise,
++ unless required by applicable law (such as deliberate and grossly
++ negligent acts) or agreed to in writing, shall any Contributor be
++ liable to You for damages, including any direct, indirect, special,
++ incidental, or consequential damages of any character arising as a
++ result of this License or out of the use or inability to use the
++ Work (including but not limited to damages for loss of goodwill,
++ work stoppage, computer failure or malfunction, or any and all
++ other commercial damages or losses), even if such Contributor
++ has been advised of the possibility of such damages.
++
++ 9. Accepting Warranty or Additional Liability. While redistributing
++ the Work or Derivative Works thereof, You may choose to offer,
++ and charge a fee for, acceptance of support, warranty, indemnity,
++ or other liability obligations and/or rights consistent with this
++ License. However, in accepting such obligations, You may act only
++ on Your own behalf and on Your sole responsibility, not on behalf
++ of any other Contributor, and only if You agree to indemnify,
++ defend, and hold each Contributor harmless for any liability
++ incurred by, or claims asserted against, such Contributor by reason
++ of your accepting any such warranty or additional liability.
++
++ END OF TERMS AND CONDITIONS
++
++ APPENDIX: How to apply the Apache License to your work.
++
++ To apply the Apache License to your work, attach the following
++ boilerplate notice, with the fields enclosed by brackets "{}"
++ replaced with your own identifying information. (Don't include
++ the brackets!) The text should be enclosed in the appropriate
++ comment syntax for the file format. We also recommend that a
++ file or class name and description of purpose be included on the
++ same "printed page" as the copyright notice for easier
++ identification within third-party archives.
++
++ Copyright {yyyy} {name of copyright owner}
++
++ Licensed under the Apache License, Version 2.0 (the "License");
++ you may not use this file except in compliance with the License.
++ You may obtain a copy of the License at
++
++ http://www.apache.org/licenses/LICENSE-2.0
++
++ Unless required by applicable law or agreed to in writing, software
++ distributed under the License is distributed on an "AS IS" BASIS,
++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ See the License for the specific language governing permissions and
++ limitations under the License.
++
+diff --git a/third_party/python/distro/MANIFEST.in b/third_party/python/distro/MANIFEST.in
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/MANIFEST.in
+@@ -0,0 +1,12 @@
++include *.md
++include *.py
++include *.txt
++include LICENSE
++include CHANGES
++include Makefile
++
++graft tests
++
++include docs/*
++
++global-exclude *.py[co]
+diff --git a/third_party/python/distro/Makefile b/third_party/python/distro/Makefile
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/Makefile
+@@ -0,0 +1,145 @@
++# Copyright 2015,2016 Nir Cohen
++#
++# Licensed under the Apache License, Version 2.0 (the "License");
++# you may not use this file except in compliance with the License.
++# You may obtain a copy of the License at
++#
++# http://www.apache.org/licenses/LICENSE-2.0
++#
++# Unless required by applicable law or agreed to in writing, software
++# distributed under the License is distributed on an "AS IS" BASIS,
++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++# See the License for the specific language governing permissions and
++# limitations under the License.
++
++# Name of this package
++PACKAGENAME = distro
++
++# Additional options for Sphinx
++SPHINXOPTS = -v
++
++# Paper format for the Sphinx LaTex/PDF builder.
++# Valid values: a4, letter
++SPHINXPAPER = a4
++
++# Sphinx build subtree.
++SPHINXBUILDDIR = build_docs
++
++# Directory where conf.py is located
++SPHINXCONFDIR = docs
++
++# Directory where input files for Sphinx are located
++SPHINXSOURCEDIR = .
++
++# Sphinx build command (Use 'pip install sphinx' to get it)
++SPHINXBUILD = sphinx-build
++
++# Internal variables for Sphinx
++SPHINXPAPEROPT_a4 = -D latex_paper_size=a4
++SPHINXPAPEROPT_letter = -D latex_paper_size=letter
++ALLSPHINXOPTS = -d $(SPHINXBUILDDIR)/doctrees -c $(SPHINXCONFDIR) \
++ $(SPHINXPAPEROPT_$(SPHINXPAPER)) $(SPHINXOPTS) \
++ $(SPHINXSOURCEDIR)
++
++.PHONY: help
++help:
++ @echo 'Please use "make <target>" where <target> is one of'
++ @echo " release - build a release and publish it"
++ @echo " dev - prepare a development environment (includes tests)"
++ @echo " instdev - prepare a development environment (no tests)"
++ @echo " install - install into current Python environment"
++ @echo " html - generate docs as standalone HTML files in: $(SPHINXBUILDDIR)/html"
++ @echo " pdf - generate docs as PDF (via LaTeX) for paper format: $(SPHINXPAPER) in: $(SPHINXBUILDDIR)/pdf"
++ @echo " man - generate docs as manual pages in: $(SPHINXBUILDDIR)/man"
++ @echo " docchanges - generate an overview of all changed/added/deprecated items in docs"
++ @echo " doclinkcheck - check all external links in docs for integrity"
++ @echo " doccoverage - run coverage check of the documentation"
++ @echo " clobber - remove any build products"
++ @echo " build - build the package"
++ @echo " test - test from this directory using tox, including test coverage"
++ @echo " publish - upload to PyPI"
++ @echo " clean - remove any temporary build products"
++ @echo " dry-run - perform all action required for a release without actually releasing"
++
++.PHONY: release
++release: test clean build publish
++ @echo "$@ done."
++
++.PHONY: test
++test:
++ pip install 'tox>=1.7.2'
++ tox
++ @echo "$@ done."
++
++.PHONY: clean
++clean:
++ rm -rf dist build $(PACKAGENAME).egg-info
++ @echo "$@ done."
++
++.PHONY: build
++build:
++ python setup.py sdist bdist_wheel
++
++.PHONY: publish
++publish:
++ twine upload -r pypi dist/$(PACKAGENAME)-*
++ @echo "$@ done."
++
++.PHONY: dry-run
++dry-run: test clean build
++ @echo "$@ done."
++
++.PHONY: dev
++dev: instdev test
++ @echo "$@ done."
++
++.PHONY: instdev
++instdev:
++ pip install -r dev-requirements.txt
++ python setup.py develop
++ @echo "$@ done."
++
++.PHONY: install
++install:
++ python setup.py install
++ @echo "$@ done."
++
++.PHONY: html
++html:
++ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(SPHINXBUILDDIR)/html
++ @echo "$@ done; the HTML pages are in $(SPHINXBUILDDIR)/html."
++
++.PHONY: pdf
++pdf:
++ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(SPHINXBUILDDIR)/pdf
++ @echo "Running LaTeX files through pdflatex..."
++ $(MAKE) -C $(SPHINXBUILDDIR)/pdf all-pdf
++ @echo "$@ done; the PDF files are in $(SPHINXBUILDDIR)/pdf."
++
++.PHONY: man
++man:
++ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(SPHINXBUILDDIR)/man
++ @echo "$@ done; the manual pages are in $(SPHINXBUILDDIR)/man."
++
++.PHONY: docchanges
++docchanges:
++ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(SPHINXBUILDDIR)/changes
++ @echo
++ @echo "$@ done; the doc changes overview file is in $(SPHINXBUILDDIR)/changes."
++
++.PHONY: doclinkcheck
++doclinkcheck:
++ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(SPHINXBUILDDIR)/linkcheck
++ @echo
++ @echo "$@ done; look for any errors in the above output " \
++ "or in $(SPHINXBUILDDIR)/linkcheck/output.txt."
++
++.PHONY: doccoverage
++doccoverage:
++ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(SPHINXBUILDDIR)/coverage
++ @echo "$@ done; the doc coverage results are in $(SPHINXBUILDDIR)/coverage/python.txt."
++
++.PHONY: clobber
++clobber: clean
++ rm -rf $(SPHINXBUILDDIR)
++ @echo "$@ done."
+diff --git a/third_party/python/distro/PKG-INFO b/third_party/python/distro/PKG-INFO
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/PKG-INFO
+@@ -0,0 +1,168 @@
++Metadata-Version: 2.1
++Name: distro
++Version: 1.4.0
++Summary: Distro - an OS platform information API
++Home-page: https://github.com/nir0s/distro
++Author: Nir Cohen
++Author-email: nir36g@gmail.com
++License: Apache License, Version 2.0
++Description: Distro - an OS platform information API
++ =======================================
++
++ [![Build Status](https://travis-ci.org/nir0s/distro.svg?branch=master)](https://travis-ci.org/nir0s/distro)
++ [![Build status](https://ci.appveyor.com/api/projects/status/e812qjk1gf0f74r5/branch/master?svg=true)](https://ci.appveyor.com/project/nir0s/distro/branch/master)
++ [![PyPI version](http://img.shields.io/pypi/v/distro.svg)](https://pypi.python.org/pypi/distro)
++ [![Supported Python Versions](https://img.shields.io/pypi/pyversions/distro.svg)](https://img.shields.io/pypi/pyversions/distro.svg)
++ [![Requirements Status](https://requires.io/github/nir0s/distro/requirements.svg?branch=master)](https://requires.io/github/nir0s/distro/requirements/?branch=master)
++ [![Code Coverage](https://codecov.io/github/nir0s/distro/coverage.svg?branch=master)](https://codecov.io/github/nir0s/distro?branch=master)
++ [![Code Quality](https://landscape.io/github/nir0s/distro/master/landscape.svg?style=flat)](https://landscape.io/github/nir0s/distro)
++ [![Is Wheel](https://img.shields.io/pypi/wheel/distro.svg?style=flat)](https://pypi.python.org/pypi/distro)
++ [![Latest Github Release](https://readthedocs.org/projects/distro/badge/?version=stable)](http://distro.readthedocs.io/en/latest/)
++ [![Join the chat at https://gitter.im/nir0s/distro](https://badges.gitter.im/nir0s/distro.svg)](https://gitter.im/nir0s/distro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
++
++ `distro` provides information about the
++ OS distribution it runs on, such as a reliable machine-readable ID, or
++ version information.
++
++ It is the recommended replacement for Python's original
++ [`platform.linux_distribution`](https://docs.python.org/3.7/library/platform.html#platform.linux_distribution)
++ function (which will be removed in Python 3.8).
++ It also provides much more functionality which isn't necessarily Python bound,
++ like a command-line interface.
++
++ Distro currently supports Linux and BSD based systems but [Windows and OS X support](https://github.com/nir0s/distro/issues/177) is also planned.
++
++ For Python 2.6 support, see https://github.com/nir0s/distro/tree/python2.6-support
++
++ ## Installation
++
++ Installation of the latest released version from PyPI:
++
++ ```shell
++ pip install distro
++ ```
++
++ Installation of the latest development version:
++
++ ```shell
++ pip install https://github.com/nir0s/distro/archive/master.tar.gz
++ ```
++
++
++ ## Usage
++
++ ```bash
++ $ distro
++ Name: Antergos Linux
++ Version: 2015.10 (ISO-Rolling)
++ Codename: ISO-Rolling
++
++ $ distro -j
++ {
++ "codename": "ISO-Rolling",
++ "id": "antergos",
++ "like": "arch",
++ "version": "16.9",
++ "version_parts": {
++ "build_number": "",
++ "major": "16",
++ "minor": "9"
++ }
++ }
++
++
++ $ python
++ >>> import distro
++ >>> distro.linux_distribution(full_distribution_name=False)
++ ('centos', '7.1.1503', 'Core')
++ ```
++
++
++ ## Documentation
++
++ On top of the aforementioned API, several more functions are available. For a complete description of the
++ API, see the [latest API documentation](http://distro.readthedocs.org/en/latest/).
++
++ ## Background
++
++ An alternative implementation became necessary because Python 3.5 deprecated
++ this function, and Python 3.8 will remove it altogether.
++ Its predecessor function `platform.dist` was already deprecated since
++ Python 2.6 and will also be removed in Python 3.8.
++ Still, there are many cases in which access to that information is needed.
++ See [Python issue 1322](https://bugs.python.org/issue1322) for more
++ information.
++
++ The `distro` package implements a robust and inclusive way of retrieving the
++ information about a distribution based on new standards and old methods,
++ namely from these data sources (from high to low precedence):
++
++ * The os-release file `/etc/os-release`, if present.
++ * The output of the `lsb_release` command, if available.
++ * The distro release file (`/etc/*(-|_)(release|version)`), if present.
++ * The `uname` command for BSD based distrubtions.
++
++
++ ## Python and Distribution Support
++
++ `distro` is supported and tested on Python 2.7, 3.4+ and PyPy and on
++ any distribution that provides one or more of the data sources
++ covered.
++
++ This package is tested with test data that mimics the exact behavior of the data sources of [a number of Linux distributions](https://github.com/nir0s/distro/tree/master/tests/resources/distros).
++
++
++ ## Testing
++
++ ```shell
++ git clone git@github.com:nir0s/distro.git
++ cd distro
++ pip install tox
++ tox
++ ```
++
++
++ ## Contributions
++
++ Pull requests are always welcome to deal with specific distributions or just
++ for general merriment.
++
++ See [CONTRIBUTIONS](https://github.com/nir0s/distro/blob/master/CONTRIBUTING.md) for contribution info.
++
++ Reference implementations for supporting additional distributions and file
++ formats can be found here:
++
++ * https://github.com/saltstack/salt/blob/develop/salt/grains/core.py#L1172
++ * https://github.com/chef/ohai/blob/master/lib/ohai/plugins/linux/platform.rb
++ * https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/facts/system/distribution.py
++ * https://github.com/puppetlabs/facter/blob/master/lib/src/facts/linux/os_linux.cc
++
++ ## Package manager distributions
++
++ * https://src.fedoraproject.org/rpms/python-distro
++ * https://www.archlinux.org/packages/community/any/python-distro/
++ * https://launchpad.net/ubuntu/+source/python-distro
++ * https://packages.debian.org/sid/python-distro
++ * https://packages.gentoo.org/packages/dev-python/distro
++ * https://pkgs.org/download/python2-distro
++ * https://slackbuilds.org/repository/14.2/python/python-distro/
++
++Platform: All
++Classifier: Development Status :: 5 - Production/Stable
++Classifier: Intended Audience :: Developers
++Classifier: Intended Audience :: System Administrators
++Classifier: License :: OSI Approved :: Apache Software License
++Classifier: Operating System :: POSIX :: Linux
++Classifier: Operating System :: POSIX :: BSD
++Classifier: Operating System :: POSIX :: BSD :: FreeBSD
++Classifier: Operating System :: POSIX :: BSD :: NetBSD
++Classifier: Operating System :: POSIX :: BSD :: OpenBSD
++Classifier: Programming Language :: Python :: 2
++Classifier: Programming Language :: Python :: 2.7
++Classifier: Programming Language :: Python :: 3
++Classifier: Programming Language :: Python :: 3.4
++Classifier: Programming Language :: Python :: 3.5
++Classifier: Programming Language :: Python :: 3.6
++Classifier: Topic :: Software Development :: Libraries :: Python Modules
++Classifier: Topic :: System :: Operating System
++Description-Content-Type: text/markdown
+diff --git a/third_party/python/distro/README.md b/third_party/python/distro/README.md
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/README.md
+@@ -0,0 +1,140 @@
++Distro - an OS platform information API
++=======================================
++
++[![Build Status](https://travis-ci.org/nir0s/distro.svg?branch=master)](https://travis-ci.org/nir0s/distro)
++[![Build status](https://ci.appveyor.com/api/projects/status/e812qjk1gf0f74r5/branch/master?svg=true)](https://ci.appveyor.com/project/nir0s/distro/branch/master)
++[![PyPI version](http://img.shields.io/pypi/v/distro.svg)](https://pypi.python.org/pypi/distro)
++[![Supported Python Versions](https://img.shields.io/pypi/pyversions/distro.svg)](https://img.shields.io/pypi/pyversions/distro.svg)
++[![Requirements Status](https://requires.io/github/nir0s/distro/requirements.svg?branch=master)](https://requires.io/github/nir0s/distro/requirements/?branch=master)
++[![Code Coverage](https://codecov.io/github/nir0s/distro/coverage.svg?branch=master)](https://codecov.io/github/nir0s/distro?branch=master)
++[![Code Quality](https://landscape.io/github/nir0s/distro/master/landscape.svg?style=flat)](https://landscape.io/github/nir0s/distro)
++[![Is Wheel](https://img.shields.io/pypi/wheel/distro.svg?style=flat)](https://pypi.python.org/pypi/distro)
++[![Latest Github Release](https://readthedocs.org/projects/distro/badge/?version=stable)](http://distro.readthedocs.io/en/latest/)
++[![Join the chat at https://gitter.im/nir0s/distro](https://badges.gitter.im/nir0s/distro.svg)](https://gitter.im/nir0s/distro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
++
++`distro` provides information about the
++OS distribution it runs on, such as a reliable machine-readable ID, or
++version information.
++
++It is the recommended replacement for Python's original
++[`platform.linux_distribution`](https://docs.python.org/3.7/library/platform.html#platform.linux_distribution)
++function (which will be removed in Python 3.8).
++It also provides much more functionality which isn't necessarily Python bound,
++like a command-line interface.
++
++Distro currently supports Linux and BSD based systems but [Windows and OS X support](https://github.com/nir0s/distro/issues/177) is also planned.
++
++For Python 2.6 support, see https://github.com/nir0s/distro/tree/python2.6-support
++
++## Installation
++
++Installation of the latest released version from PyPI:
++
++```shell
++pip install distro
++```
++
++Installation of the latest development version:
++
++```shell
++pip install https://github.com/nir0s/distro/archive/master.tar.gz
++```
++
++
++## Usage
++
++```bash
++$ distro
++Name: Antergos Linux
++Version: 2015.10 (ISO-Rolling)
++Codename: ISO-Rolling
++
++$ distro -j
++{
++ "codename": "ISO-Rolling",
++ "id": "antergos",
++ "like": "arch",
++ "version": "16.9",
++ "version_parts": {
++ "build_number": "",
++ "major": "16",
++ "minor": "9"
++ }
++}
++
++
++$ python
++>>> import distro
++>>> distro.linux_distribution(full_distribution_name=False)
++('centos', '7.1.1503', 'Core')
++```
++
++
++## Documentation
++
++On top of the aforementioned API, several more functions are available. For a complete description of the
++API, see the [latest API documentation](http://distro.readthedocs.org/en/latest/).
++
++## Background
++
++An alternative implementation became necessary because Python 3.5 deprecated
++this function, and Python 3.8 will remove it altogether.
++Its predecessor function `platform.dist` was already deprecated since
++Python 2.6 and will also be removed in Python 3.8.
++Still, there are many cases in which access to that information is needed.
++See [Python issue 1322](https://bugs.python.org/issue1322) for more
++information.
++
++The `distro` package implements a robust and inclusive way of retrieving the
++information about a distribution based on new standards and old methods,
++namely from these data sources (from high to low precedence):
++
++* The os-release file `/etc/os-release`, if present.
++* The output of the `lsb_release` command, if available.
++* The distro release file (`/etc/*(-|_)(release|version)`), if present.
++* The `uname` command for BSD based distrubtions.
++
++
++## Python and Distribution Support
++
++`distro` is supported and tested on Python 2.7, 3.4+ and PyPy and on
++any distribution that provides one or more of the data sources
++covered.
++
++This package is tested with test data that mimics the exact behavior of the data sources of [a number of Linux distributions](https://github.com/nir0s/distro/tree/master/tests/resources/distros).
++
++
++## Testing
++
++```shell
++git clone git@github.com:nir0s/distro.git
++cd distro
++pip install tox
++tox
++```
++
++
++## Contributions
++
++Pull requests are always welcome to deal with specific distributions or just
++for general merriment.
++
++See [CONTRIBUTIONS](https://github.com/nir0s/distro/blob/master/CONTRIBUTING.md) for contribution info.
++
++Reference implementations for supporting additional distributions and file
++formats can be found here:
++
++* https://github.com/saltstack/salt/blob/develop/salt/grains/core.py#L1172
++* https://github.com/chef/ohai/blob/master/lib/ohai/plugins/linux/platform.rb
++* https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/facts/system/distribution.py
++* https://github.com/puppetlabs/facter/blob/master/lib/src/facts/linux/os_linux.cc
++
++## Package manager distributions
++
++* https://src.fedoraproject.org/rpms/python-distro
++* https://www.archlinux.org/packages/community/any/python-distro/
++* https://launchpad.net/ubuntu/+source/python-distro
++* https://packages.debian.org/sid/python-distro
++* https://packages.gentoo.org/packages/dev-python/distro
++* https://pkgs.org/download/python2-distro
++* https://slackbuilds.org/repository/14.2/python/python-distro/
+diff --git a/third_party/python/distro/dev-requirements.txt b/third_party/python/distro/dev-requirements.txt
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/dev-requirements.txt
+@@ -0,0 +1,3 @@
++pytest
++pytest-cov
++sphinx>=1.1
+\ No newline at end of file
+diff --git a/third_party/python/distro/distro.py b/third_party/python/distro/distro.py
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/distro.py
+@@ -0,0 +1,1216 @@
++# Copyright 2015,2016,2017 Nir Cohen
++#
++# Licensed under the Apache License, Version 2.0 (the "License");
++# you may not use this file except in compliance with the License.
++# You may obtain a copy of the License at
++#
++# http://www.apache.org/licenses/LICENSE-2.0
++#
++# Unless required by applicable law or agreed to in writing, software
++# distributed under the License is distributed on an "AS IS" BASIS,
++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++# See the License for the specific language governing permissions and
++# limitations under the License.
++
++"""
++The ``distro`` package (``distro`` stands for Linux Distribution) provides
++information about the Linux distribution it runs on, such as a reliable
++machine-readable distro ID, or version information.
++
++It is the recommended replacement for Python's original
++:py:func:`platform.linux_distribution` function, but it provides much more
++functionality. An alternative implementation became necessary because Python
++3.5 deprecated this function, and Python 3.8 will remove it altogether.
++Its predecessor function :py:func:`platform.dist` was already
++deprecated since Python 2.6 and will also be removed in Python 3.8.
++Still, there are many cases in which access to OS distribution information
++is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for
++more information.
++"""
++
++import os
++import re
++import sys
++import json
++import shlex
++import logging
++import argparse
++import subprocess
++
++
++_UNIXCONFDIR = os.environ.get('UNIXCONFDIR', '/etc')
++_OS_RELEASE_BASENAME = 'os-release'
++
++#: Translation table for normalizing the "ID" attribute defined in os-release
++#: files, for use by the :func:`distro.id` method.
++#:
++#: * Key: Value as defined in the os-release file, translated to lower case,
++#: with blanks translated to underscores.
++#:
++#: * Value: Normalized value.
++NORMALIZED_OS_ID = {
++ 'ol': 'oracle', # Oracle Enterprise Linux
++}
++
++#: Translation table for normalizing the "Distributor ID" attribute returned by
++#: the lsb_release command, for use by the :func:`distro.id` method.
++#:
++#: * Key: Value as returned by the lsb_release command, translated to lower
++#: case, with blanks translated to underscores.
++#:
++#: * Value: Normalized value.
++NORMALIZED_LSB_ID = {
++ 'enterpriseenterprise': 'oracle', # Oracle Enterprise Linux
++ 'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation
++ 'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server
++}
++
++#: Translation table for normalizing the distro ID derived from the file name
++#: of distro release files, for use by the :func:`distro.id` method.
++#:
++#: * Key: Value as derived from the file name of a distro release file,
++#: translated to lower case, with blanks translated to underscores.
++#:
++#: * Value: Normalized value.
++NORMALIZED_DISTRO_ID = {
++ 'redhat': 'rhel', # RHEL 6.x, 7.x
++}
++
++# Pattern for content of distro release file (reversed)
++_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
++ r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)')
++
++# Pattern for base file name of distro release file
++_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(
++ r'(\w+)[-_](release|version)$')
++
++# Base file names to be ignored when searching for distro release file
++_DISTRO_RELEASE_IGNORE_BASENAMES = (
++ 'debian_version',
++ 'lsb-release',
++ 'oem-release',
++ _OS_RELEASE_BASENAME,
++ 'system-release'
++)
++
++
++def linux_distribution(full_distribution_name=True):
++ """
++ Return information about the current OS distribution as a tuple
++ ``(id_name, version, codename)`` with items as follows:
++
++ * ``id_name``: If *full_distribution_name* is false, the result of
++ :func:`distro.id`. Otherwise, the result of :func:`distro.name`.
++
++ * ``version``: The result of :func:`distro.version`.
++
++ * ``codename``: The result of :func:`distro.codename`.
++
++ The interface of this function is compatible with the original
++ :py:func:`platform.linux_distribution` function, supporting a subset of
++ its parameters.
++
++ The data it returns may not exactly be the same, because it uses more data
++ sources than the original function, and that may lead to different data if
++ the OS distribution is not consistent across multiple data sources it
++ provides (there are indeed such distributions ...).
++
++ Another reason for differences is the fact that the :func:`distro.id`
++ method normalizes the distro ID string to a reliable machine-readable value
++ for a number of popular OS distributions.
++ """
++ return _distro.linux_distribution(full_distribution_name)
++
++
++def id():
++ """
++ Return the distro ID of the current distribution, as a
++ machine-readable string.
++
++ For a number of OS distributions, the returned distro ID value is
++ *reliable*, in the sense that it is documented and that it does not change
++ across releases of the distribution.
++
++ This package maintains the following reliable distro ID values:
++
++ ============== =========================================
++ Distro ID Distribution
++ ============== =========================================
++ "ubuntu" Ubuntu
++ "debian" Debian
++ "rhel" RedHat Enterprise Linux
++ "centos" CentOS
++ "fedora" Fedora
++ "sles" SUSE Linux Enterprise Server
++ "opensuse" openSUSE
++ "amazon" Amazon Linux
++ "arch" Arch Linux
++ "cloudlinux" CloudLinux OS
++ "exherbo" Exherbo Linux
++ "gentoo" GenToo Linux
++ "ibm_powerkvm" IBM PowerKVM
++ "kvmibm" KVM for IBM z Systems
++ "linuxmint" Linux Mint
++ "mageia" Mageia
++ "mandriva" Mandriva Linux
++ "parallels" Parallels
++ "pidora" Pidora
++ "raspbian" Raspbian
++ "oracle" Oracle Linux (and Oracle Enterprise Linux)
++ "scientific" Scientific Linux
++ "slackware" Slackware
++ "xenserver" XenServer
++ "openbsd" OpenBSD
++ "netbsd" NetBSD
++ "freebsd" FreeBSD
++ ============== =========================================
++
++ If you have a need to get distros for reliable IDs added into this set,
++ or if you find that the :func:`distro.id` function returns a different
++ distro ID for one of the listed distros, please create an issue in the
++ `distro issue tracker`_.
++
++ **Lookup hierarchy and transformations:**
++
++ First, the ID is obtained from the following sources, in the specified
++ order. The first available and non-empty value is used:
++
++ * the value of the "ID" attribute of the os-release file,
++
++ * the value of the "Distributor ID" attribute returned by the lsb_release
++ command,
++
++ * the first part of the file name of the distro release file,
++
++ The so determined ID value then passes the following transformations,
++ before it is returned by this method:
++
++ * it is translated to lower case,
++
++ * blanks (which should not be there anyway) are translated to underscores,
++
++ * a normalization of the ID is performed, based upon
++ `normalization tables`_. The purpose of this normalization is to ensure
++ that the ID is as reliable as possible, even across incompatible changes
++ in the OS distributions. A common reason for an incompatible change is
++ the addition of an os-release file, or the addition of the lsb_release
++ command, with ID values that differ from what was previously determined
++ from the distro release file name.
++ """
++ return _distro.id()
++
++
++def name(pretty=False):
++ """
++ Return the name of the current OS distribution, as a human-readable
++ string.
++
++ If *pretty* is false, the name is returned without version or codename.
++ (e.g. "CentOS Linux")
++
++ If *pretty* is true, the version and codename are appended.
++ (e.g. "CentOS Linux 7.1.1503 (Core)")
++
++ **Lookup hierarchy:**
++
++ The name is obtained from the following sources, in the specified order.
++ The first available and non-empty value is used:
++
++ * If *pretty* is false:
++
++ - the value of the "NAME" attribute of the os-release file,
++
++ - the value of the "Distributor ID" attribute returned by the lsb_release
++ command,
++
++ - the value of the "<name>" field of the distro release file.
++
++ * If *pretty* is true:
++
++ - the value of the "PRETTY_NAME" attribute of the os-release file,
++
++ - the value of the "Description" attribute returned by the lsb_release
++ command,
++
++ - the value of the "<name>" field of the distro release file, appended
++ with the value of the pretty version ("<version_id>" and "<codename>"
++ fields) of the distro release file, if available.
++ """
++ return _distro.name(pretty)
++
++
++def version(pretty=False, best=False):
++ """
++ Return the version of the current OS distribution, as a human-readable
++ string.
++
++ If *pretty* is false, the version is returned without codename (e.g.
++ "7.0").
++
++ If *pretty* is true, the codename in parenthesis is appended, if the
++ codename is non-empty (e.g. "7.0 (Maipo)").
++
++ Some distributions provide version numbers with different precisions in
++ the different sources of distribution information. Examining the different
++ sources in a fixed priority order does not always yield the most precise
++ version (e.g. for Debian 8.2, or CentOS 7.1).
++
++ The *best* parameter can be used to control the approach for the returned
++ version:
++
++ If *best* is false, the first non-empty version number in priority order of
++ the examined sources is returned.
++
++ If *best* is true, the most precise version number out of all examined
++ sources is returned.
++
++ **Lookup hierarchy:**
++
++ In all cases, the version number is obtained from the following sources.
++ If *best* is false, this order represents the priority order:
++
++ * the value of the "VERSION_ID" attribute of the os-release file,
++ * the value of the "Release" attribute returned by the lsb_release
++ command,
++ * the version number parsed from the "<version_id>" field of the first line
++ of the distro release file,
++ * the version number parsed from the "PRETTY_NAME" attribute of the
++ os-release file, if it follows the format of the distro release files.
++ * the version number parsed from the "Description" attribute returned by
++ the lsb_release command, if it follows the format of the distro release
++ files.
++ """
++ return _distro.version(pretty, best)
++
++
++def version_parts(best=False):
++ """
++ Return the version of the current OS distribution as a tuple
++ ``(major, minor, build_number)`` with items as follows:
++
++ * ``major``: The result of :func:`distro.major_version`.
++
++ * ``minor``: The result of :func:`distro.minor_version`.
++
++ * ``build_number``: The result of :func:`distro.build_number`.
++
++ For a description of the *best* parameter, see the :func:`distro.version`
++ method.
++ """
++ return _distro.version_parts(best)
++
++
++def major_version(best=False):
++ """
++ Return the major version of the current OS distribution, as a string,
++ if provided.
++ Otherwise, the empty string is returned. The major version is the first
++ part of the dot-separated version string.
++
++ For a description of the *best* parameter, see the :func:`distro.version`
++ method.
++ """
++ return _distro.major_version(best)
++
++
++def minor_version(best=False):
++ """
++ Return the minor version of the current OS distribution, as a string,
++ if provided.
++ Otherwise, the empty string is returned. The minor version is the second
++ part of the dot-separated version string.
++
++ For a description of the *best* parameter, see the :func:`distro.version`
++ method.
++ """
++ return _distro.minor_version(best)
++
++
++def build_number(best=False):
++ """
++ Return the build number of the current OS distribution, as a string,
++ if provided.
++ Otherwise, the empty string is returned. The build number is the third part
++ of the dot-separated version string.
++
++ For a description of the *best* parameter, see the :func:`distro.version`
++ method.
++ """
++ return _distro.build_number(best)
++
++
++def like():
++ """
++ Return a space-separated list of distro IDs of distributions that are
++ closely related to the current OS distribution in regards to packaging
++ and programming interfaces, for example distributions the current
++ distribution is a derivative from.
++
++ **Lookup hierarchy:**
++
++ This information item is only provided by the os-release file.
++ For details, see the description of the "ID_LIKE" attribute in the
++ `os-release man page
++ <http://www.freedesktop.org/software/systemd/man/os-release.html>`_.
++ """
++ return _distro.like()
++
++
++def codename():
++ """
++ Return the codename for the release of the current OS distribution,
++ as a string.
++
++ If the distribution does not have a codename, an empty string is returned.
++
++ Note that the returned codename is not always really a codename. For
++ example, openSUSE returns "x86_64". This function does not handle such
++ cases in any special way and just returns the string it finds, if any.
++
++ **Lookup hierarchy:**
++
++ * the codename within the "VERSION" attribute of the os-release file, if
++ provided,
++
++ * the value of the "Codename" attribute returned by the lsb_release
++ command,
++
++ * the value of the "<codename>" field of the distro release file.
++ """
++ return _distro.codename()
++
++
++def info(pretty=False, best=False):
++ """
++ Return certain machine-readable information items about the current OS
++ distribution in a dictionary, as shown in the following example:
++
++ .. sourcecode:: python
++
++ {
++ 'id': 'rhel',
++ 'version': '7.0',
++ 'version_parts': {
++ 'major': '7',
++ 'minor': '0',
++ 'build_number': ''
++ },
++ 'like': 'fedora',
++ 'codename': 'Maipo'
++ }
++
++ The dictionary structure and keys are always the same, regardless of which
++ information items are available in the underlying data sources. The values
++ for the various keys are as follows:
++
++ * ``id``: The result of :func:`distro.id`.
++
++ * ``version``: The result of :func:`distro.version`.
++
++ * ``version_parts -> major``: The result of :func:`distro.major_version`.
++
++ * ``version_parts -> minor``: The result of :func:`distro.minor_version`.
++
++ * ``version_parts -> build_number``: The result of
++ :func:`distro.build_number`.
++
++ * ``like``: The result of :func:`distro.like`.
++
++ * ``codename``: The result of :func:`distro.codename`.
++
++ For a description of the *pretty* and *best* parameters, see the
++ :func:`distro.version` method.
++ """
++ return _distro.info(pretty, best)
++
++
++def os_release_info():
++ """
++ Return a dictionary containing key-value pairs for the information items
++ from the os-release file data source of the current OS distribution.
++
++ See `os-release file`_ for details about these information items.
++ """
++ return _distro.os_release_info()
++
++
++def lsb_release_info():
++ """
++ Return a dictionary containing key-value pairs for the information items
++ from the lsb_release command data source of the current OS distribution.
++
++ See `lsb_release command output`_ for details about these information
++ items.
++ """
++ return _distro.lsb_release_info()
++
++
++def distro_release_info():
++ """
++ Return a dictionary containing key-value pairs for the information items
++ from the distro release file data source of the current OS distribution.
++
++ See `distro release file`_ for details about these information items.
++ """
++ return _distro.distro_release_info()
++
++
++def uname_info():
++ """
++ Return a dictionary containing key-value pairs for the information items
++ from the distro release file data source of the current OS distribution.
++ """
++ return _distro.uname_info()
++
++
++def os_release_attr(attribute):
++ """
++ Return a single named information item from the os-release file data source
++ of the current OS distribution.
++
++ Parameters:
++
++ * ``attribute`` (string): Key of the information item.
++
++ Returns:
++
++ * (string): Value of the information item, if the item exists.
++ The empty string, if the item does not exist.
++
++ See `os-release file`_ for details about these information items.
++ """
++ return _distro.os_release_attr(attribute)
++
++
++def lsb_release_attr(attribute):
++ """
++ Return a single named information item from the lsb_release command output
++ data source of the current OS distribution.
++
++ Parameters:
++
++ * ``attribute`` (string): Key of the information item.
++
++ Returns:
++
++ * (string): Value of the information item, if the item exists.
++ The empty string, if the item does not exist.
++
++ See `lsb_release command output`_ for details about these information
++ items.
++ """
++ return _distro.lsb_release_attr(attribute)
++
++
++def distro_release_attr(attribute):
++ """
++ Return a single named information item from the distro release file
++ data source of the current OS distribution.
++
++ Parameters:
++
++ * ``attribute`` (string): Key of the information item.
++
++ Returns:
++
++ * (string): Value of the information item, if the item exists.
++ The empty string, if the item does not exist.
++
++ See `distro release file`_ for details about these information items.
++ """
++ return _distro.distro_release_attr(attribute)
++
++
++def uname_attr(attribute):
++ """
++ Return a single named information item from the distro release file
++ data source of the current OS distribution.
++
++ Parameters:
++
++ * ``attribute`` (string): Key of the information item.
++
++ Returns:
++
++ * (string): Value of the information item, if the item exists.
++ The empty string, if the item does not exist.
++ """
++ return _distro.uname_attr(attribute)
++
++
++class cached_property(object):
++ """A version of @property which caches the value. On access, it calls the
++ underlying function and sets the value in `__dict__` so future accesses
++ will not re-call the property.
++ """
++ def __init__(self, f):
++ self._fname = f.__name__
++ self._f = f
++
++ def __get__(self, obj, owner):
++ assert obj is not None, 'call {} on an instance'.format(self._fname)
++ ret = obj.__dict__[self._fname] = self._f(obj)
++ return ret
++
++
++class LinuxDistribution(object):
++ """
++ Provides information about a OS distribution.
++
++ This package creates a private module-global instance of this class with
++ default initialization arguments, that is used by the
++ `consolidated accessor functions`_ and `single source accessor functions`_.
++ By using default initialization arguments, that module-global instance
++ returns data about the current OS distribution (i.e. the distro this
++ package runs on).
++
++ Normally, it is not necessary to create additional instances of this class.
++ However, in situations where control is needed over the exact data sources
++ that are used, instances of this class can be created with a specific
++ distro release file, or a specific os-release file, or without invoking the
++ lsb_release command.
++ """
++
++ def __init__(self,
++ include_lsb=True,
++ os_release_file='',
++ distro_release_file='',
++ include_uname=True):
++ """
++ The initialization method of this class gathers information from the
++ available data sources, and stores that in private instance attributes.
++ Subsequent access to the information items uses these private instance
++ attributes, so that the data sources are read only once.
++
++ Parameters:
++
++ * ``include_lsb`` (bool): Controls whether the
++ `lsb_release command output`_ is included as a data source.
++
++ If the lsb_release command is not available in the program execution
++ path, the data source for the lsb_release command will be empty.
++
++ * ``os_release_file`` (string): The path name of the
++ `os-release file`_ that is to be used as a data source.
++
++ An empty string (the default) will cause the default path name to
++ be used (see `os-release file`_ for details).
++
++ If the specified or defaulted os-release file does not exist, the
++ data source for the os-release file will be empty.
++
++ * ``distro_release_file`` (string): The path name of the
++ `distro release file`_ that is to be used as a data source.
++
++ An empty string (the default) will cause a default search algorithm
++ to be used (see `distro release file`_ for details).
++
++ If the specified distro release file does not exist, or if no default
++ distro release file can be found, the data source for the distro
++ release file will be empty.
++
++ * ``include_name`` (bool): Controls whether uname command output is
++ included as a data source. If the uname command is not available in
++ the program execution path the data source for the uname command will
++ be empty.
++
++ Public instance attributes:
++
++ * ``os_release_file`` (string): The path name of the
++ `os-release file`_ that is actually used as a data source. The
++ empty string if no distro release file is used as a data source.
++
++ * ``distro_release_file`` (string): The path name of the
++ `distro release file`_ that is actually used as a data source. The
++ empty string if no distro release file is used as a data source.
++
++ * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.
++ This controls whether the lsb information will be loaded.
++
++ * ``include_uname`` (bool): The result of the ``include_uname``
++ parameter. This controls whether the uname information will
++ be loaded.
++
++ Raises:
++
++ * :py:exc:`IOError`: Some I/O issue with an os-release file or distro
++ release file.
++
++ * :py:exc:`subprocess.CalledProcessError`: The lsb_release command had
++ some issue (other than not being available in the program execution
++ path).
++
++ * :py:exc:`UnicodeError`: A data source has unexpected characters or
++ uses an unexpected encoding.
++ """
++ self.os_release_file = os_release_file or \
++ os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME)
++ self.distro_release_file = distro_release_file or '' # updated later
++ self.include_lsb = include_lsb
++ self.include_uname = include_uname
++
++ def __repr__(self):
++ """Return repr of all info
++ """
++ return \
++ "LinuxDistribution(" \
++ "os_release_file={self.os_release_file!r}, " \
++ "distro_release_file={self.distro_release_file!r}, " \
++ "include_lsb={self.include_lsb!r}, " \
++ "include_uname={self.include_uname!r}, " \
++ "_os_release_info={self._os_release_info!r}, " \
++ "_lsb_release_info={self._lsb_release_info!r}, " \
++ "_distro_release_info={self._distro_release_info!r}, " \
++ "_uname_info={self._uname_info!r})".format(
++ self=self)
++
++ def linux_distribution(self, full_distribution_name=True):
++ """
++ Return information about the OS distribution that is compatible
++ with Python's :func:`platform.linux_distribution`, supporting a subset
++ of its parameters.
++
++ For details, see :func:`distro.linux_distribution`.
++ """
++ return (
++ self.name() if full_distribution_name else self.id(),
++ self.version(),
++ self.codename()
++ )
++
++ def id(self):
++ """Return the distro ID of the OS distribution, as a string.
++
++ For details, see :func:`distro.id`.
++ """
++ def normalize(distro_id, table):
++ distro_id = distro_id.lower().replace(' ', '_')
++ return table.get(distro_id, distro_id)
++
++ distro_id = self.os_release_attr('id')
++ if distro_id:
++ return normalize(distro_id, NORMALIZED_OS_ID)
++
++ distro_id = self.lsb_release_attr('distributor_id')
++ if distro_id:
++ return normalize(distro_id, NORMALIZED_LSB_ID)
++
++ distro_id = self.distro_release_attr('id')
++ if distro_id:
++ return normalize(distro_id, NORMALIZED_DISTRO_ID)
++
++ distro_id = self.uname_attr('id')
++ if distro_id:
++ return normalize(distro_id, NORMALIZED_DISTRO_ID)
++
++ return ''
++
++ def name(self, pretty=False):
++ """
++ Return the name of the OS distribution, as a string.
++
++ For details, see :func:`distro.name`.
++ """
++ name = self.os_release_attr('name') \
++ or self.lsb_release_attr('distributor_id') \
++ or self.distro_release_attr('name') \
++ or self.uname_attr('name')
++ if pretty:
++ name = self.os_release_attr('pretty_name') \
++ or self.lsb_release_attr('description')
++ if not name:
++ name = self.distro_release_attr('name') \
++ or self.uname_attr('name')
++ version = self.version(pretty=True)
++ if version:
++ name = name + ' ' + version
++ return name or ''
++
++ def version(self, pretty=False, best=False):
++ """
++ Return the version of the OS distribution, as a string.
++
++ For details, see :func:`distro.version`.
++ """
++ versions = [
++ self.os_release_attr('version_id'),
++ self.lsb_release_attr('release'),
++ self.distro_release_attr('version_id'),
++ self._parse_distro_release_content(
++ self.os_release_attr('pretty_name')).get('version_id', ''),
++ self._parse_distro_release_content(
++ self.lsb_release_attr('description')).get('version_id', ''),
++ self.uname_attr('release')
++ ]
++ version = ''
++ if best:
++ # This algorithm uses the last version in priority order that has
++ # the best precision. If the versions are not in conflict, that
++ # does not matter; otherwise, using the last one instead of the
++ # first one might be considered a surprise.
++ for v in versions:
++ if v.count(".") > version.count(".") or version == '':
++ version = v
++ else:
++ for v in versions:
++ if v != '':
++ version = v
++ break
++ if pretty and version and self.codename():
++ version = u'{0} ({1})'.format(version, self.codename())
++ return version
++
++ def version_parts(self, best=False):
++ """
++ Return the version of the OS distribution, as a tuple of version
++ numbers.
++
++ For details, see :func:`distro.version_parts`.
++ """
++ version_str = self.version(best=best)
++ if version_str:
++ version_regex = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?')
++ matches = version_regex.match(version_str)
++ if matches:
++ major, minor, build_number = matches.groups()
++ return major, minor or '', build_number or ''
++ return '', '', ''
++
++ def major_version(self, best=False):
++ """
++ Return the major version number of the current distribution.
++
++ For details, see :func:`distro.major_version`.
++ """
++ return self.version_parts(best)[0]
++
++ def minor_version(self, best=False):
++ """
++ Return the minor version number of the current distribution.
++
++ For details, see :func:`distro.minor_version`.
++ """
++ return self.version_parts(best)[1]
++
++ def build_number(self, best=False):
++ """
++ Return the build number of the current distribution.
++
++ For details, see :func:`distro.build_number`.
++ """
++ return self.version_parts(best)[2]
++
++ def like(self):
++ """
++ Return the IDs of distributions that are like the OS distribution.
++
++ For details, see :func:`distro.like`.
++ """
++ return self.os_release_attr('id_like') or ''
++
++ def codename(self):
++ """
++ Return the codename of the OS distribution.
++
++ For details, see :func:`distro.codename`.
++ """
++ try:
++ # Handle os_release specially since distros might purposefully set
++ # this to empty string to have no codename
++ return self._os_release_info['codename']
++ except KeyError:
++ return self.lsb_release_attr('codename') \
++ or self.distro_release_attr('codename') \
++ or ''
++
++ def info(self, pretty=False, best=False):
++ """
++ Return certain machine-readable information about the OS
++ distribution.
++
++ For details, see :func:`distro.info`.
++ """
++ return dict(
++ id=self.id(),
++ version=self.version(pretty, best),
++ version_parts=dict(
++ major=self.major_version(best),
++ minor=self.minor_version(best),
++ build_number=self.build_number(best)
++ ),
++ like=self.like(),
++ codename=self.codename(),
++ )
++
++ def os_release_info(self):
++ """
++ Return a dictionary containing key-value pairs for the information
++ items from the os-release file data source of the OS distribution.
++
++ For details, see :func:`distro.os_release_info`.
++ """
++ return self._os_release_info
++
++ def lsb_release_info(self):
++ """
++ Return a dictionary containing key-value pairs for the information
++ items from the lsb_release command data source of the OS
++ distribution.
++
++ For details, see :func:`distro.lsb_release_info`.
++ """
++ return self._lsb_release_info
++
++ def distro_release_info(self):
++ """
++ Return a dictionary containing key-value pairs for the information
++ items from the distro release file data source of the OS
++ distribution.
++
++ For details, see :func:`distro.distro_release_info`.
++ """
++ return self._distro_release_info
++
++ def uname_info(self):
++ """
++ Return a dictionary containing key-value pairs for the information
++ items from the uname command data source of the OS distribution.
++
++ For details, see :func:`distro.uname_info`.
++ """
++ return self._uname_info
++
++ def os_release_attr(self, attribute):
++ """
++ Return a single named information item from the os-release file data
++ source of the OS distribution.
++
++ For details, see :func:`distro.os_release_attr`.
++ """
++ return self._os_release_info.get(attribute, '')
++
++ def lsb_release_attr(self, attribute):
++ """
++ Return a single named information item from the lsb_release command
++ output data source of the OS distribution.
++
++ For details, see :func:`distro.lsb_release_attr`.
++ """
++ return self._lsb_release_info.get(attribute, '')
++
++ def distro_release_attr(self, attribute):
++ """
++ Return a single named information item from the distro release file
++ data source of the OS distribution.
++
++ For details, see :func:`distro.distro_release_attr`.
++ """
++ return self._distro_release_info.get(attribute, '')
++
++ def uname_attr(self, attribute):
++ """
++ Return a single named information item from the uname command
++ output data source of the OS distribution.
++
++ For details, see :func:`distro.uname_release_attr`.
++ """
++ return self._uname_info.get(attribute, '')
++
++ @cached_property
++ def _os_release_info(self):
++ """
++ Get the information items from the specified os-release file.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ if os.path.isfile(self.os_release_file):
++ with open(self.os_release_file) as release_file:
++ return self._parse_os_release_content(release_file)
++ return {}
++
++ @staticmethod
++ def _parse_os_release_content(lines):
++ """
++ Parse the lines of an os-release file.
++
++ Parameters:
++
++ * lines: Iterable through the lines in the os-release file.
++ Each line must be a unicode string or a UTF-8 encoded byte
++ string.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ props = {}
++ lexer = shlex.shlex(lines, posix=True)
++ lexer.whitespace_split = True
++
++ # The shlex module defines its `wordchars` variable using literals,
++ # making it dependent on the encoding of the Python source file.
++ # In Python 2.6 and 2.7, the shlex source file is encoded in
++ # 'iso-8859-1', and the `wordchars` variable is defined as a byte
++ # string. This causes a UnicodeDecodeError to be raised when the
++ # parsed content is a unicode object. The following fix resolves that
++ # (... but it should be fixed in shlex...):
++ if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):
++ lexer.wordchars = lexer.wordchars.decode('iso-8859-1')
++
++ tokens = list(lexer)
++ for token in tokens:
++ # At this point, all shell-like parsing has been done (i.e.
++ # comments processed, quotes and backslash escape sequences
++ # processed, multi-line values assembled, trailing newlines
++ # stripped, etc.), so the tokens are now either:
++ # * variable assignments: var=value
++ # * commands or their arguments (not allowed in os-release)
++ if '=' in token:
++ k, v = token.split('=', 1)
++ if isinstance(v, bytes):
++ v = v.decode('utf-8')
++ props[k.lower()] = v
++ else:
++ # Ignore any tokens that are not variable assignments
++ pass
++
++ if 'version_codename' in props:
++ # os-release added a version_codename field. Use that in
++ # preference to anything else Note that some distros purposefully
++ # do not have code names. They should be setting
++ # version_codename=""
++ props['codename'] = props['version_codename']
++ elif 'ubuntu_codename' in props:
++ # Same as above but a non-standard field name used on older Ubuntus
++ props['codename'] = props['ubuntu_codename']
++ elif 'version' in props:
++ # If there is no version_codename, parse it from the version
++ codename = re.search(r'(\(\D+\))|,(\s+)?\D+', props['version'])
++ if codename:
++ codename = codename.group()
++ codename = codename.strip('()')
++ codename = codename.strip(',')
++ codename = codename.strip()
++ # codename appears within paranthese.
++ props['codename'] = codename
++
++ return props
++
++ @cached_property
++ def _lsb_release_info(self):
++ """
++ Get the information items from the lsb_release command output.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ if not self.include_lsb:
++ return {}
++ with open(os.devnull, 'w') as devnull:
++ try:
++ cmd = ('lsb_release', '-a')
++ stdout = subprocess.check_output(cmd, stderr=devnull)
++ except OSError: # Command not found
++ return {}
++ content = stdout.decode(sys.getfilesystemencoding()).splitlines()
++ return self._parse_lsb_release_content(content)
++
++ @staticmethod
++ def _parse_lsb_release_content(lines):
++ """
++ Parse the output of the lsb_release command.
++
++ Parameters:
++
++ * lines: Iterable through the lines of the lsb_release output.
++ Each line must be a unicode string or a UTF-8 encoded byte
++ string.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ props = {}
++ for line in lines:
++ kv = line.strip('\n').split(':', 1)
++ if len(kv) != 2:
++ # Ignore lines without colon.
++ continue
++ k, v = kv
++ props.update({k.replace(' ', '_').lower(): v.strip()})
++ return props
++
++ @cached_property
++ def _uname_info(self):
++ with open(os.devnull, 'w') as devnull:
++ try:
++ cmd = ('uname', '-rs')
++ stdout = subprocess.check_output(cmd, stderr=devnull)
++ except OSError:
++ return {}
++ content = stdout.decode(sys.getfilesystemencoding()).splitlines()
++ return self._parse_uname_content(content)
++
++ @staticmethod
++ def _parse_uname_content(lines):
++ props = {}
++ match = re.search(r'^([^\s]+)\s+([\d\.]+)', lines[0].strip())
++ if match:
++ name, version = match.groups()
++
++ # This is to prevent the Linux kernel version from
++ # appearing as the 'best' version on otherwise
++ # identifiable distributions.
++ if name == 'Linux':
++ return {}
++ props['id'] = name.lower()
++ props['name'] = name
++ props['release'] = version
++ return props
++
++ @cached_property
++ def _distro_release_info(self):
++ """
++ Get the information items from the specified distro release file.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ if self.distro_release_file:
++ # If it was specified, we use it and parse what we can, even if
++ # its file name or content does not match the expected pattern.
++ distro_info = self._parse_distro_release_file(
++ self.distro_release_file)
++ basename = os.path.basename(self.distro_release_file)
++ # The file name pattern for user-specified distro release files
++ # is somewhat more tolerant (compared to when searching for the
++ # file), because we want to use what was specified as best as
++ # possible.
++ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
++ if 'name' in distro_info \
++ and 'cloudlinux' in distro_info['name'].lower():
++ distro_info['id'] = 'cloudlinux'
++ elif match:
++ distro_info['id'] = match.group(1)
++ return distro_info
++ else:
++ try:
++ basenames = os.listdir(_UNIXCONFDIR)
++ # We sort for repeatability in cases where there are multiple
++ # distro specific files; e.g. CentOS, Oracle, Enterprise all
++ # containing `redhat-release` on top of their own.
++ basenames.sort()
++ except OSError:
++ # This may occur when /etc is not readable but we can't be
++ # sure about the *-release files. Check common entries of
++ # /etc for information. If they turn out to not be there the
++ # error is handled in `_parse_distro_release_file()`.
++ basenames = ['SuSE-release',
++ 'arch-release',
++ 'base-release',
++ 'centos-release',
++ 'fedora-release',
++ 'gentoo-release',
++ 'mageia-release',
++ 'mandrake-release',
++ 'mandriva-release',
++ 'mandrivalinux-release',
++ 'manjaro-release',
++ 'oracle-release',
++ 'redhat-release',
++ 'sl-release',
++ 'slackware-version']
++ for basename in basenames:
++ if basename in _DISTRO_RELEASE_IGNORE_BASENAMES:
++ continue
++ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
++ if match:
++ filepath = os.path.join(_UNIXCONFDIR, basename)
++ distro_info = self._parse_distro_release_file(filepath)
++ if 'name' in distro_info:
++ # The name is always present if the pattern matches
++ self.distro_release_file = filepath
++ distro_info['id'] = match.group(1)
++ if 'cloudlinux' in distro_info['name'].lower():
++ distro_info['id'] = 'cloudlinux'
++ return distro_info
++ return {}
++
++ def _parse_distro_release_file(self, filepath):
++ """
++ Parse a distro release file.
++
++ Parameters:
++
++ * filepath: Path name of the distro release file.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ try:
++ with open(filepath) as fp:
++ # Only parse the first line. For instance, on SLES there
++ # are multiple lines. We don't want them...
++ return self._parse_distro_release_content(fp.readline())
++ except (OSError, IOError):
++ # Ignore not being able to read a specific, seemingly version
++ # related file.
++ # See https://github.com/nir0s/distro/issues/162
++ return {}
++
++ @staticmethod
++ def _parse_distro_release_content(line):
++ """
++ Parse a line from a distro release file.
++
++ Parameters:
++ * line: Line from the distro release file. Must be a unicode string
++ or a UTF-8 encoded byte string.
++
++ Returns:
++ A dictionary containing all information items.
++ """
++ if isinstance(line, bytes):
++ line = line.decode('utf-8')
++ matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(
++ line.strip()[::-1])
++ distro_info = {}
++ if matches:
++ # regexp ensures non-None
++ distro_info['name'] = matches.group(3)[::-1]
++ if matches.group(2):
++ distro_info['version_id'] = matches.group(2)[::-1]
++ if matches.group(1):
++ distro_info['codename'] = matches.group(1)[::-1]
++ elif line:
++ distro_info['name'] = line.strip()
++ return distro_info
++
++
++_distro = LinuxDistribution()
++
++
++def main():
++ logger = logging.getLogger(__name__)
++ logger.setLevel(logging.DEBUG)
++ logger.addHandler(logging.StreamHandler(sys.stdout))
++
++ parser = argparse.ArgumentParser(description="OS distro info tool")
++ parser.add_argument(
++ '--json',
++ '-j',
++ help="Output in machine readable format",
++ action="store_true")
++ args = parser.parse_args()
++
++ if args.json:
++ logger.info(json.dumps(info(), indent=4, sort_keys=True))
++ else:
++ logger.info('Name: %s', name(pretty=True))
++ distribution_version = version(pretty=True)
++ logger.info('Version: %s', distribution_version)
++ distribution_codename = codename()
++ logger.info('Codename: %s', distribution_codename)
++
++
++if __name__ == '__main__':
++ main()
+diff --git a/third_party/python/distro/docs/conf.py b/third_party/python/distro/docs/conf.py
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/docs/conf.py
+@@ -0,0 +1,342 @@
++# -*- coding: utf-8 -*-
++#
++# Configuration file for Sphinx builds, created by
++# sphinx-quickstart on Wed Mar 2 11:33:06 2016.
++#
++# This file is execfile()d with the current directory set to its
++# containing dir.
++#
++# Note that not all possible configuration values are present in this
++# autogenerated file.
++#
++# All configuration values have a default; values that are commented out
++# serve to show the default.
++
++import sys
++import os
++import re
++
++# If extensions (or modules to document with autodoc) are in another directory,
++# add these directories to sys.path here. If the directory is relative to the
++# documentation root, use os.path.abspath to make it absolute, like shown here.
++sys.path.insert(0, os.path.abspath('..'))
++
++# -- General configuration ------------------------------------------------
++
++# If your documentation needs a minimal Sphinx version, state it here.
++needs_sphinx = '1.1'
++
++# Add any Sphinx extension module names here, as strings. They can be
++# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
++# ones.
++extensions = [
++ 'sphinx.ext.autodoc',
++ 'sphinx.ext.intersphinx',
++ 'sphinx.ext.todo',
++ 'sphinx.ext.coverage',
++ 'sphinx.ext.viewcode',
++]
++
++# Add any paths that contain templates here, relative to this directory.
++templates_path = ['_templates']
++
++# The suffix(es) of source filenames.
++# You can specify multiple suffix as a list of string:
++# source_suffix = ['.rst', '.md']
++source_suffix = '.rst'
++
++# The encoding of source files.
++source_encoding = 'utf-8'
++
++# The master toctree document.
++on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
++if on_rtd:
++ master_doc = 'index'
++else:
++ master_doc = 'docs/index'
++
++# General information about the project.
++project = u'distro'
++copyright = u'2015,2016, Nir Cohen, Andreas Maier'
++author = u'Nir Cohen, Andreas Maier'
++
++# The short description of the package.
++_short_description = u'Linux Distribution - a Linux OS platform information API'
++
++# The version info for the project you're documenting, acts as replacement for
++# |version| and |release|, also used in various other places throughout the
++# built documents.
++
++def parse_version():
++ with open('../setup.py', 'r') as _fp:
++ _lines = _fp.readlines()
++ for _line in _lines:
++ m = re.match(r'^package_version *= *[\'"](.+)[\'"].*$', _line)
++ if m:
++ break
++ if m:
++ return m.group(1)
++ else:
++ return 'unknown'
++
++# The short X.Y version.
++# Note: We use the full version in both cases.
++version = parse_version()
++
++# The full version, including alpha/beta/rc tags.
++release = version
++
++# The language for content autogenerated by Sphinx. Refer to documentation
++# for a list of supported languages.
++#
++# This is also used if you do content translation via gettext catalogs.
++# Usually you set "language" from the command line for these cases.
++language = None
++
++# There are two options for replacing |today|: either, you set today to some
++# non-false value, then it is used:
++#today = ''
++# Else, today_fmt is used as the format for a strftime call.
++#today_fmt = '%B %d, %Y'
++
++# List of patterns, relative to source directory, that match files and
++# directories to ignore when looking for source files.
++exclude_patterns = ["tests", ".tox", ".git", "build_docs", "ld.egg-info"]
++
++# The reST default role (used for this markup: `text`) to use for all
++# documents.
++#default_role = None
++
++# If true, '()' will be appended to :func: etc. cross-reference text.
++add_function_parentheses = True
++
++# If true, the current module name will be prepended to all description
++# unit titles (such as .. function::).
++#add_module_names = True
++
++# If true, sectionauthor and moduleauthor directives will be shown in the
++# output. They are ignored by default.
++#show_authors = False
++
++# The name of the Pygments (syntax highlighting) style to use.
++pygments_style = 'sphinx'
++
++# A list of ignored prefixes for module index sorting.
++#modindex_common_prefix = []
++
++# If true, keep warnings as "system message" paragraphs in the built documents.
++#keep_warnings = False
++
++# If true, `todo` and `todoList` produce output, else they produce nothing.
++todo_include_todos = True
++
++
++# -- Options for HTML output ----------------------------------------------
++
++# The theme to use for HTML and HTML Help pages.
++# See http://www.sphinx-doc.org/en/stable/theming.html for built-in themes.
++html_theme = "classic"
++
++# Theme options are theme-specific and customize the look and feel of a theme
++# further.
++# See http://www.sphinx-doc.org/en/stable/theming.html for the options
++# available for built-in themes.
++html_theme_options = {
++}
++
++# Add any paths that contain custom themes here, relative to this directory.
++#html_theme_path = []
++
++# The name for this set of Sphinx documents. If not defined, it defaults to
++# "<project> v<release> documentation".
++#html_title = None
++
++# A shorter title for the navigation bar. Default is the same as html_title.
++#html_short_title = 'distro'
++
++# The name of an image file (relative to this directory) to place at the top
++# of the sidebar.
++#html_logo = None
++
++# The name of an image file (relative to this directory) to use as a favicon of
++# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
++# pixels large.
++#html_favicon = None
++
++# Add any paths that contain custom static files (such as style sheets) here,
++# relative to this directory. They are copied after the builtin static files,
++# so a file named "default.css" will overwrite the builtin "default.css".
++html_static_path = ['html_static']
++
++# Add any extra paths that contain custom files (such as robots.txt or
++# .htaccess) here, relative to this directory. These files are copied
++# directly to the root of the documentation.
++html_extra_path = ['html_extra']
++
++# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
++# using the given strftime format.
++#html_last_updated_fmt = '%b %d, %Y'
++
++# If true, SmartyPants will be used to convert quotes and dashes to
++# typographically correct entities.
++#html_use_smartypants = True
++
++# Custom sidebar templates, maps document names to template names.
++#html_sidebars = {}
++
++# Additional templates that should be rendered to pages, maps page names to
++# template names.
++#html_additional_pages = {}
++
++# If false, no module index is generated.
++#html_domain_indices = True
++
++# If false, no index is generated.
++#html_use_index = True
++
++# If true, the index is split into individual pages for each letter.
++#html_split_index = False
++
++# If true, links to the reST sources are added to the pages.
++#html_show_sourcelink = True
++
++# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
++#html_show_sphinx = True
++
++# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
++#html_show_copyright = True
++
++# If true, an OpenSearch description file will be output, and all pages will
++# contain a <link> tag referring to it. The value of this option must be the
++# base URL from which the finished HTML is served.
++#html_use_opensearch = ''
++
++# This is the file name suffix for HTML files (e.g. ".xhtml").
++#html_file_suffix = None
++
++# Language to be used for generating the HTML full-text search index.
++# Sphinx supports the following languages:
++# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
++# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
++#html_search_language = 'en'
++
++# A dictionary with options for the search language support, empty by default.
++# Now only 'ja' uses this config value
++#html_search_options = {'type': 'default'}
++
++# The name of a javascript file (relative to the configuration directory) that
++# implements a search results scorer. If empty, the default will be used.
++#html_search_scorer = 'scorer.js'
++
++# Output file base name for HTML help builder.
++htmlhelp_basename = 'distro_doc'
++
++# -- Options for LaTeX output ---------------------------------------------
++
++latex_elements = {
++# The paper size ('letterpaper' or 'a4paper').
++#'papersize': 'letterpaper',
++
++# The font size ('10pt', '11pt' or '12pt').
++#'pointsize': '10pt',
++
++# Additional stuff for the LaTeX preamble.
++#'preamble': '',
++
++# Latex figure (float) alignment
++#'figure_align': 'htbp',
++}
++
++# Grouping the document tree into LaTeX files. List of tuples
++# (source start file, target name, title,
++# author, documentclass [howto, manual, or own class]).
++latex_documents = [
++ (master_doc, 'ld.tex', _short_description, author, 'manual'),
++]
++
++# The name of an image file (relative to this directory) to place at the top of
++# the title page.
++#latex_logo = None
++
++# For "manual" documents, if this is true, then toplevel headings are parts,
++# not chapters.
++#latex_use_parts = False
++
++# If true, show page references after internal links.
++#latex_show_pagerefs = False
++
++# If true, show URL addresses after external links.
++#latex_show_urls = False
++
++# Documents to append as an appendix to all manuals.
++#latex_appendices = []
++
++# If false, no module index is generated.
++#latex_domain_indices = True
++
++
++# -- Options for manual page output ---------------------------------------
++
++# One entry per manual page. List of tuples
++# (source start file, name, description, authors, manual section).
++man_pages = [
++ (master_doc, 'ld', _short_description, [author], 1)
++]
++
++# If true, show URL addresses after external links.
++#man_show_urls = False
++
++
++# -- Options for Texinfo output -------------------------------------------
++
++# Grouping the document tree into Texinfo files. List of tuples
++# (source start file, target name, title, author,
++# dir menu entry, description, category)
++texinfo_documents = [
++ (master_doc, 'LinuxDistribution', _short_description,
++ author, 'LinuxDistribution', _short_description,
++ 'Miscellaneous'),
++]
++
++# Documents to append as an appendix to all manuals.
++#texinfo_appendices = []
++
++# If false, no module index is generated.
++#texinfo_domain_indices = True
++
++# How to display URL addresses: 'footnote', 'no', or 'inline'.
++#texinfo_show_urls = 'footnote'
++
++# If true, do not generate a @detailmenu in the "Top" node's menu.
++#texinfo_no_detailmenu = False
++
++
++# -- Options for autodoc extension ----------------------------------------
++# For documentation, see
++# http://www.sphinx-doc.org/en/stable/ext/autodoc.html
++
++# Selects what content will be inserted into a class description.
++# The possible values are:
++# "class" - Only the class’ docstring is inserted. This is the default.
++# "both" - Both the class’ and the __init__ method’s docstring are
++# concatenated and inserted.
++# "init" - Only the __init__ method’s docstring is inserted.
++autoclass_content = "both"
++
++# Selects if automatically documented members are sorted alphabetically
++# (value 'alphabetical'), by member type (value 'groupwise') or by source
++# order (value 'bysource'). The default is alphabetical.
++autodoc_member_order = "bysource"
++
++# -- Options for intersphinx extension ------------------------------------
++# For documentation, see
++# http://www.sphinx-doc.org/en/stable/ext/intersphinx.html
++
++# Defines the prefixes for intersphinx links, and the targets they resolve
++# to. Example RST source for 'py' prefix:
++# :py:func:`platform.dist`
++intersphinx_mapping = {
++ 'py': ('https://docs.python.org/3.5', None)
++}
++
++intersphinx_cache_limit = 5
+diff --git a/third_party/python/distro/docs/index.rst b/third_party/python/distro/docs/index.rst
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/docs/index.rst
+@@ -0,0 +1,476 @@
++
++.. _distro official repo: https://github.com/nir0s/distro
++.. _distro issue tracker: https://github.com/nir0s/distro/issues
++.. _open issues on missing test data: https://github.com/nir0s/distro/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22>
++
++
++**distro** package (Linux Distribution) version |version|
++*********************************************************
++
++Official distro repository: `distro official repo`_
++
++Overview and motivation
++=======================
++
++.. automodule:: distro
++
++If you want to jump into the API description right away, read about the
++`consolidated accessor functions`_.
++
++Compatibility
++=============
++
++The ``distro`` package is supported on Python 2.7, 3.4+ and PyPy, and on
++any Linux or *BSD distribution that provides one or more of the `data sources`_
++used by this package.
++
++This package is tested on Python 2.7, 3.4+ and PyPy, with test data that
++mimics the exact behavior of the data sources of
++`a number of Linux distributions <https://github.com/nir0s/distro/tree/master/tests/resources/distros>`_.
++
++If you want to add test data for more distributions, please
++create an issue in the `distro issue tracker`_
++and provide the following information in the issue:
++
++* The content of the `/etc/os-release` file, if any.
++* The file names and content of the `/etc/*release` and `/etc/*version` files, if any.
++* The output of the command: `lsb_release -a`, if available.
++* The file names and content of any other files you are aware of that provide
++ useful information about the distro.
++
++There are already some `open issues on missing test data`_.
++
++
++Data sources
++============
++
++The ``distro`` package implements a robust and inclusive way of retrieving the
++information about a Linux distribution based on new standards and old methods,
++namely from these data sources:
++
++* The `os-release file`_, if present.
++
++* The `lsb_release command output`_, if the lsb_release command is available.
++
++* The `distro release file`_, if present.
++
++* The `uname command output`_, if present.
++
++
++Access to the information
++=========================
++
++This package provides three ways to access the information about a Linux
++distribution:
++
++* `Consolidated accessor functions`_
++
++ These are module-global functions that take into account all data sources in
++ a priority order, and that return information about the current Linux
++ distribution.
++
++ These functions should be the normal way to access the information.
++
++ The precedence of data sources is applied for each information item
++ separately. Therefore, it is possible that not all information items returned
++ by these functions come from the same data source. For example, on a
++ distribution that has an lsb_release command that returns the
++ "Distributor ID" field but not the "Codename" field, and that has a distro
++ release file that specifies a codename inside, the distro ID will come from
++ the lsb_release command (because it has higher precedence), and the codename
++ will come from the distro release file (because it is not provided by the
++ lsb_release command).
++
++ Examples: :func:`distro.id` for retrieving
++ the distro ID, or :func:`ld.info` to get the machine-readable part of the
++ information in a more aggregated way, or :func:`distro.linux_distribution` with
++ an interface that is compatible to the original
++ :py:func:`platform.linux_distribution` function, supporting a subset of its
++ parameters.
++
++* `Single source accessor functions`_
++
++ These are module-global functions that take into account a single data
++ source, and that return information about the current Linux distribution.
++
++ They are useful for distributions that provide multiple inconsistent data
++ sources, or for retrieving information items that are not provided by the
++ consolidated accessor functions.
++
++ Examples: :func:`distro.os_release_attr` for retrieving a single information
++ item from the os-release data source, or :func:`distro.lsb_release_info` for
++ retrieving all information items from the lsb_release command output data
++ source.
++
++* `LinuxDistribution class`_
++
++ The :class:`distro.LinuxDistribution` class provides the main code of this
++ package.
++
++ This package contains a private module-global :class:`distro.LinuxDistribution`
++ instance with default initialization arguments, that is used by the
++ consolidated and single source accessor functions.
++
++ A user-defined instance of the :class:`distro.LinuxDistribution` class allows
++ specifying the path names of the os-release file and distro release file and
++ whether the lsb_release command should be used or not. That is useful for
++ example when the distribution information from a chrooted environment
++ is to be retrieved, or when a distro has multiple distro release files and
++ the default algorithm uses the wrong one.
++
++
++Consolidated accessor functions
++===============================
++
++This section describes the consolidated accessor functions.
++See `access to the information`_ for a discussion of the different kinds of
++accessor functions.
++
++.. autofunction:: distro.linux_distribution
++.. autofunction:: distro.id
++.. autofunction:: distro.name
++.. autofunction:: distro.version
++.. autofunction:: distro.version_parts
++.. autofunction:: distro.major_version
++.. autofunction:: distro.minor_version
++.. autofunction:: distro.build_number
++.. autofunction:: distro.like
++.. autofunction:: distro.codename
++.. autofunction:: distro.info
++
++Single source accessor functions
++================================
++
++This section describes the single source accessor functions.
++See `access to the information`_ for a discussion of the different kinds of
++accessor functions.
++
++.. autofunction:: distro.os_release_info
++.. autofunction:: distro.lsb_release_info
++.. autofunction:: distro.distro_release_info
++.. autofunction:: distro.os_release_attr
++.. autofunction:: distro.lsb_release_attr
++.. autofunction:: distro.distro_release_attr
++
++LinuxDistribution class
++=======================
++
++This section describes the access via the :class:`distro.LinuxDistribution` class.
++See `access to the information`_ for a discussion of the different kinds of
++accessor functions.
++
++.. autoclass:: distro.LinuxDistribution
++ :members:
++ :undoc-members:
++
++Normalization tables
++====================
++
++These translation tables are used to normalize the parsed distro ID values
++into reliable IDs. See :func:`distro.id` for details.
++
++They are documented in order to show for which distros a normalization is
++currently defined.
++
++As a quick fix, these tables can also be extended by the user by appending new
++entries, should the need arise. If you have a need to get these tables
++extended, please make an according request in the `distro issue tracker`_.
++
++.. autodata:: distro.NORMALIZED_OS_ID
++.. autodata:: distro.NORMALIZED_LSB_ID
++.. autodata:: distro.NORMALIZED_DISTRO_ID
++
++Os-release file
++===============
++
++The os-release file is looked up using the path name ``/etc/os-release``. Its
++optional additional location ``/usr/lib/os-release`` is ignored.
++
++The os-release file is expected to be encoded in UTF-8.
++
++It is parsed using the standard Python :py:mod:`shlex` package, which treats it
++like a shell script.
++
++The attribute names found in the file are translated to lower case and then
++become the keys of the information items from the os-release file data source.
++These keys can be used to retrieve single items with the
++:func:`distro.os_release_attr` function, and they are also used as keys in the
++dictionary returned by :func:`distro.os_release_info`.
++
++The attribute values found in the file are processed using shell rules (e.g.
++for whitespace, escaping, and quoting) before they become the values of the
++information items from the os-release file data source.
++
++If the attribute "VERSION" is found in the file, the distro codename is
++extracted from its value if it can be found there. If a codename is found, it
++becomes an additional information item with key "codename".
++
++See the `os-release man page
++<http://www.freedesktop.org/software/systemd/man/os-release.html>`_
++for a list of possible attributes in the file.
++
++**Examples:**
++
++1. The following os-release file content:
++
++ .. sourcecode:: shell
++
++ NAME='Ubuntu'
++ VERSION="14.04.3 LTS, Trusty Tahr"
++ ID=ubuntu
++ ID_LIKE=debian
++ PRETTY_NAME="Ubuntu 14.04.3 LTS"
++ VERSION_ID="14.04"
++ HOME_URL="http://www.ubuntu.com/"
++ SUPPORT_URL="http://help.ubuntu.com/"
++ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ name "Ubuntu"
++ version "14.04.3 LTS, Trusty Tahr"
++ id "ubuntu"
++ id_like "debian"
++ pretty_name "Ubuntu 14.04.3 LTS"
++ version_id "14.04"
++ home_url "http://www.ubuntu.com/"
++ support_url "http://help.ubuntu.com/"
++ bug_report_url "http://bugs.launchpad.net/ubuntu/"
++ codename "Trusty Tahr"
++ =============================== ==========================================
++
++2. The following os-release file content:
++
++ .. sourcecode:: shell
++
++ NAME="Red Hat Enterprise Linux Server"
++ VERSION="7.0 (Maipo)"
++ ID="rhel"
++ ID_LIKE="fedora"
++ VERSION_ID="7.0"
++ PRETTY_NAME="Red Hat Enterprise Linux Server 7.0 (Maipo)"
++ ANSI_COLOR="0;31"
++ CPE_NAME="cpe:/o:redhat:enterprise_linux:7.0:GA:server"
++ HOME_URL="https://www.redhat.com/"
++ BUG_REPORT_URL="https://bugzilla.redhat.com/"
++
++ REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
++ REDHAT_BUGZILLA_PRODUCT_VERSION=7.0
++ REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
++ REDHAT_SUPPORT_PRODUCT_VERSION=7.0
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ name "Red Hat Enterprise Linux Server"
++ version "7.0 (Maipo)"
++ id "rhel"
++ id_like "fedora"
++ version_id "7.0"
++ pretty_name "Red Hat Enterprise Linux Server 7.0 (Maipo)"
++ ansi_color "0;31"
++ cpe_name "cpe:/o:redhat:enterprise_linux:7.0:GA:server"
++ home_url "https://www.redhat.com/"
++ bug_report_url "https://bugzilla.redhat.com/"
++ redhat_bugzilla_product "Red Hat Enterprise Linux 7"
++ redhat_bugzilla_product_version "7.0"
++ redhat_support_product "Red Hat Enterprise Linux"
++ redhat_support_product_version "7.0"
++ codename "Maipo"
++ =============================== ==========================================
++
++Lsb_release command output
++==========================
++
++The lsb_release command is expected to be in the PATH, and is invoked as
++follows:
++
++.. sourcecode:: shell
++
++ lsb_release -a
++
++The command output is expected to be encoded in UTF-8.
++
++Only lines in the command output with the following format will be used:
++
++ ``<attr-name>: <attr-value>``
++
++Where:
++
++* ``<attr-name>`` is the name of the attribute, and
++* ``<attr-value>`` is the attribute value.
++
++The attribute names are stripped from surrounding blanks, any remaining blanks
++are translated to underscores, they are translated to lower case, and then
++become the keys of the information items from the lsb_release command output
++data source.
++
++The attribute values are stripped from surrounding blanks, and then become the
++values of the information items from the lsb_release command output data
++source.
++
++See the `lsb_release man page
++<http://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/
++LSB-Core-generic/lsbrelease.html>`_
++for a description of standard attributes returned by the lsb_release command.
++
++**Examples:**
++
++1. The following lsb_release command output:
++
++ .. sourcecode:: text
++
++ No LSB modules are available.
++ Distributor ID: Ubuntu
++ Description: Ubuntu 14.04.3 LTS
++ Release: 14.04
++ Codename: trusty
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ distributor_id "Ubuntu"
++ description "Ubuntu 14.04.3 LTS"
++ release "14.04"
++ codename "trusty"
++ =============================== ==========================================
++
++2. The following lsb_release command output:
++
++ .. sourcecode:: text
++
++ LSB Version: n/a
++ Distributor ID: SUSE LINUX
++ Description: SUSE Linux Enterprise Server 12 SP1
++ Release: 12.1
++ Codename: n/a
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ lsb_version "n/a"
++ distributor_id "SUSE LINUX"
++ description "SUSE Linux Enterprise Server 12 SP1"
++ release "12.1"
++ codename "n/a"
++ =============================== ==========================================
++
++Distro release file
++===================
++
++Unless specified with a particular path name when using the
++:class:`distro.LinuxDistribution` class, the distro release file is found by using
++the first match in the alphabetically sorted list of the files matching the
++following path name patterns:
++
++* ``/etc/*-release``
++* ``/etc/*_release``
++* ``/etc/*-version``
++* ``/etc/*_version``
++
++where the following special path names are excluded:
++
++* ``/etc/debian_version``
++* ``/etc/system-release``
++* ``/etc/os-release``
++
++and where the first line within the file has the expected format.
++
++The algorithm to sort the files alphabetically is far from perfect, but the
++distro release file has the least priority as a data source, and it is expected
++that distributions provide one of the other data sources.
++
++The distro release file is expected to be encoded in UTF-8.
++
++Only its first line is used, and it is expected to have the following format:
++
++ ``<name> [[[release] <version_id>] (<codename>)]``
++
++Where:
++
++* square brackets indicate optionality,
++* ``<name>`` is the distro name,
++* ``<version_id>`` is the distro version, and
++* ``<codename>`` is the distro codename.
++
++The following information items can be found in a distro release file
++(shown with their keys and data types):
++
++* ``id`` (string): Distro ID, taken from the first part of the file name
++ before the hyphen (``-``) or underscore (``_``).
++
++ Note that the distro ID is not normalized or translated to lower case at this
++ point; this happens only for the result of the :func:`distro.id` function.
++
++* ``name`` (string): Distro name, as found in the first line of the file.
++
++* ``version_id`` (string): Distro version, as found in the first line of the
++ file. If not found, this information item will not exist.
++
++* ``codename`` (string): Distro codename, as found in the first line of the
++ file. If not found, this information item will not exist.
++
++ Note that the string in the codename field is not always really a
++ codename. For example, openSUSE returns "x86_64".
++
++**Examples:**
++
++1. The following distro release file ``/etc/centos-release``:
++
++ .. sourcecode:: text
++
++ CentOS Linux release 7.1.1503 (Core)
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ id "centos"
++ name "CentOS Linux"
++ version_id "7.1.1503"
++ codename "Core"
++ =============================== ==========================================
++
++2. The following distro release file ``/etc/oracle-release``:
++
++ .. sourcecode:: text
++
++ Oracle Linux Server release 7.1
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ id "oracle"
++ name "Oracle Linux Server"
++ version_id "7.1"
++ =============================== ==========================================
++
++3. The following distro release file ``/etc/SuSE-release``:
++
++ .. sourcecode:: text
++
++ openSUSE 42.1 (x86_64)
++
++ results in these information items:
++
++ =============================== ==========================================
++ Key Value
++ =============================== ==========================================
++ id "SuSE"
++ name "openSUSE"
++ version_id "42.1"
++ codename "x86_64"
++ =============================== ==========================================
++
+diff --git a/third_party/python/distro/query_local_distro.py b/third_party/python/distro/query_local_distro.py
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/query_local_distro.py
+@@ -0,0 +1,45 @@
++#!/usr/bin/env python
++# Copyright 2015,2016 Nir Cohen
++#
++# Licensed under the Apache License, Version 2.0 (the "License");
++# you may not use this file except in compliance with the License.
++# You may obtain a copy of the License at
++#
++# http://www.apache.org/licenses/LICENSE-2.0
++#
++# Unless required by applicable law or agreed to in writing, software
++# distributed under the License is distributed on an "AS IS" BASIS,
++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++# See the License for the specific language governing permissions and
++# limitations under the License.
++
++from __future__ import print_function
++
++from pprint import pformat
++
++import distro
++
++
++def pprint(obj):
++ for line in pformat(obj).split('\n'):
++ print(4 * ' ' + line)
++
++
++print('os_release_info:')
++pprint(distro.os_release_info())
++print('lsb_release_info:')
++pprint(distro.lsb_release_info())
++print('distro_release_info:')
++pprint(distro.distro_release_info())
++print('id: {0}'.format(distro.id()))
++print('name: {0}'.format(distro.name()))
++print('name_pretty: {0}'.format(distro.name(True)))
++print('version: {0}'.format(distro.version()))
++print('version_pretty: {0}'.format(distro.version(True)))
++print('like: {0}'.format(distro.like()))
++print('codename: {0}'.format(distro.codename()))
++print('linux_distribution_full: {0}'.format(distro.linux_distribution()))
++print('linux_distribution: {0}'.format(distro.linux_distribution(False)))
++print('major_version: {0}'.format(distro.major_version()))
++print('minor_version: {0}'.format(distro.minor_version()))
++print('build_number: {0}'.format(distro.build_number()))
+diff --git a/third_party/python/distro/setup.cfg b/third_party/python/distro/setup.cfg
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/setup.cfg
+@@ -0,0 +1,10 @@
++[bdist_wheel]
++universal = 1
++
++[metadata]
++license_file = LICENSE
++
++[egg_info]
++tag_build =
++tag_date = 0
++
+diff --git a/third_party/python/distro/setup.py b/third_party/python/distro/setup.py
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/setup.py
+@@ -0,0 +1,67 @@
++# Copyright 2015,2016 Nir Cohen
++#
++# Licensed under the Apache License, Version 2.0 (the "License");
++# you may not use this file except in compliance with the License.
++# You may obtain a copy of the License at
++#
++# http://www.apache.org/licenses/LICENSE-2.0
++#
++# Unless required by applicable law or agreed to in writing, software
++# distributed under the License is distributed on an "AS IS" BASIS,
++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++# See the License for the specific language governing permissions and
++# limitations under the License.
++
++import os
++import codecs
++from setuptools import setup
++
++# The following version is parsed by other parts of this package.
++# Don't change the format of the line, or the variable name.
++package_version = "1.4.0"
++
++here = os.path.abspath(os.path.dirname(__file__))
++
++
++def read(*parts):
++ # intentionally *not* adding an encoding option to open
++ return codecs.open(os.path.join(here, *parts), 'r').read()
++
++
++setup(
++ name='distro',
++ version=package_version,
++ url='https://github.com/nir0s/distro',
++ author='Nir Cohen',
++ author_email='nir36g@gmail.com',
++ license='Apache License, Version 2.0',
++ platforms='All',
++ description='Distro - an OS platform information API',
++ long_description=read('README.md'),
++ long_description_content_type='text/markdown',
++ py_modules=['distro'],
++ entry_points={
++ 'console_scripts': [
++ 'distro = distro:main',
++ ]
++ },
++ classifiers=[
++ 'Development Status :: 5 - Production/Stable',
++ 'Intended Audience :: Developers',
++ 'Intended Audience :: System Administrators',
++ 'License :: OSI Approved :: Apache Software License',
++ 'Operating System :: POSIX :: Linux',
++ 'Operating System :: POSIX :: BSD',
++ 'Operating System :: POSIX :: BSD :: FreeBSD',
++ 'Operating System :: POSIX :: BSD :: NetBSD',
++ 'Operating System :: POSIX :: BSD :: OpenBSD',
++ 'Programming Language :: Python :: 2',
++ 'Programming Language :: Python :: 2.7',
++ 'Programming Language :: Python :: 3',
++ 'Programming Language :: Python :: 3.4',
++ 'Programming Language :: Python :: 3.5',
++ 'Programming Language :: Python :: 3.6',
++ 'Topic :: Software Development :: Libraries :: Python Modules',
++ 'Topic :: System :: Operating System',
++ ]
++)
+diff --git a/third_party/python/distro/tests/__init__.py b/third_party/python/distro/tests/__init__.py
+new file mode 100644
+diff --git a/third_party/python/distro/tests/resources/distros/__shared__/bin/lsb_release b/third_party/python/distro/tests/resources/distros/__shared__/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/__shared__/bin/lsb_release
+@@ -0,0 +1,43 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++if [[ -n $LSB_VERSION ]]; then
++ echo "LSB Version: $LSB_VERSION"
++else
++ echo "No LSB modules are available."
++fi
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/amazon2014/etc/system-release b/third_party/python/distro/tests/resources/distros/amazon2014/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/amazon2014/etc/system-release
+@@ -0,0 +1,1 @@
++Amazon Linux AMI release 2014.03
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/amazon2016/etc/os-release b/third_party/python/distro/tests/resources/distros/amazon2016/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/amazon2016/etc/os-release
+@@ -0,0 +1,9 @@
++NAME="Amazon Linux AMI"
++VERSION="2016.03"
++ID="amzn"
++ID_LIKE="rhel fedora"
++VERSION_ID="2016.03"
++PRETTY_NAME="Amazon Linux AMI 2016.03"
++ANSI_COLOR="0;33"
++CPE_NAME="cpe:/o:amazon:linux:2016.03:ga"
++HOME_URL="http://aws.amazon.com/amazon-linux-ami/"
+diff --git a/third_party/python/distro/tests/resources/distros/amazon2016/etc/system-release b/third_party/python/distro/tests/resources/distros/amazon2016/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/amazon2016/etc/system-release
+@@ -0,0 +1,1 @@
++Amazon Linux AMI release 2016.03
+diff --git a/third_party/python/distro/tests/resources/distros/arch/etc/arch-release b/third_party/python/distro/tests/resources/distros/arch/etc/arch-release
+new file mode 100644
+diff --git a/third_party/python/distro/tests/resources/distros/arch/etc/os-release b/third_party/python/distro/tests/resources/distros/arch/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/arch/etc/os-release
+@@ -0,0 +1,7 @@
++NAME="Arch Linux"
++ID=arch
++PRETTY_NAME="Arch Linux"
++ANSI_COLOR="0;36"
++HOME_URL="https://www.archlinux.org/"
++SUPPORT_URL="https://bbs.archlinux.org/"
++BUG_REPORT_URL="https://bugs.archlinux.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/arch/usr/lib/os-release b/third_party/python/distro/tests/resources/distros/arch/usr/lib/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/arch/usr/lib/os-release
+@@ -0,0 +1,7 @@
++NAME="Arch Linux"
++ID=arch
++PRETTY_NAME="Arch Linux"
++ANSI_COLOR="0;36"
++HOME_URL="https://www.archlinux.org/"
++SUPPORT_URL="https://bbs.archlinux.org/"
++BUG_REPORT_URL="https://bugs.archlinux.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/centos5/etc/centos-release b/third_party/python/distro/tests/resources/distros/centos5/etc/centos-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos5/etc/centos-release
+@@ -0,0 +1,1 @@
++CentOS release 5.11 (Final)
+diff --git a/third_party/python/distro/tests/resources/distros/centos5/etc/redhat-release b/third_party/python/distro/tests/resources/distros/centos5/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos5/etc/redhat-release
+@@ -0,0 +1,1 @@
++CentOS release 5.11 (Final)
+diff --git a/third_party/python/distro/tests/resources/distros/centos5/etc/system-release b/third_party/python/distro/tests/resources/distros/centos5/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos5/etc/system-release
+@@ -0,0 +1,1 @@
++CentOS release 5.11 (Final)
+diff --git a/third_party/python/distro/tests/resources/distros/centos7/etc/centos-release b/third_party/python/distro/tests/resources/distros/centos7/etc/centos-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos7/etc/centos-release
+@@ -0,0 +1,1 @@
++CentOS Linux release 7.1.1503 (Core)
+diff --git a/third_party/python/distro/tests/resources/distros/centos7/etc/os-release b/third_party/python/distro/tests/resources/distros/centos7/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos7/etc/os-release
+@@ -0,0 +1,16 @@
++NAME="CentOS Linux"
++VERSION="7 (Core)"
++ID="centos"
++ID_LIKE="rhel fedora"
++VERSION_ID="7"
++PRETTY_NAME="CentOS Linux 7 (Core)"
++ANSI_COLOR="0;31"
++CPE_NAME="cpe:/o:centos:centos:7"
++HOME_URL="https://www.centos.org/"
++BUG_REPORT_URL="https://bugs.centos.org/"
++
++CENTOS_MANTISBT_PROJECT="CentOS-7"
++CENTOS_MANTISBT_PROJECT_VERSION="7"
++REDHAT_SUPPORT_PRODUCT="centos"
++REDHAT_SUPPORT_PRODUCT_VERSION="7"
++
+diff --git a/third_party/python/distro/tests/resources/distros/centos7/etc/redhat-release b/third_party/python/distro/tests/resources/distros/centos7/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos7/etc/redhat-release
+@@ -0,0 +1,1 @@
++CentOS Linux release 7.1.1503 (Core)
+diff --git a/third_party/python/distro/tests/resources/distros/centos7/etc/system-release b/third_party/python/distro/tests/resources/distros/centos7/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/centos7/etc/system-release
+@@ -0,0 +1,1 @@
++CentOS Linux release 7.1.1503 (Core)
+diff --git a/third_party/python/distro/tests/resources/distros/cloudlinux5/etc/redhat-release b/third_party/python/distro/tests/resources/distros/cloudlinux5/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/cloudlinux5/etc/redhat-release
+@@ -0,0 +1,1 @@
++CloudLinux Server release 5.11 (Vladislav Volkov)
+diff --git a/third_party/python/distro/tests/resources/distros/cloudlinux6/etc/redhat-release b/third_party/python/distro/tests/resources/distros/cloudlinux6/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/cloudlinux6/etc/redhat-release
+@@ -0,0 +1,1 @@
++CloudLinux Server release 6.8 (Oleg Makarov)
+diff --git a/third_party/python/distro/tests/resources/distros/cloudlinux7/etc/os-release b/third_party/python/distro/tests/resources/distros/cloudlinux7/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/cloudlinux7/etc/os-release
+@@ -0,0 +1,10 @@
++NAME="CloudLinux"
++VERSION="7.3 (Yury Malyshev)"
++ID="cloudlinux"
++ID_LIKE="rhel fedora centos"
++VERSION_ID="7.3"
++PRETTY_NAME="CloudLinux 7.3 (Yury Malyshev)"
++ANSI_COLOR="0:31"
++CPE_NAME="cpe:/o:cloudlinux:cloudlinux:7.3:GA:server"
++HOME_URL="https://www.cloudlinux.com/"
++BUG_REPORT_URL="https://helpdesk.cloudlinux.com/"
+diff --git a/third_party/python/distro/tests/resources/distros/cloudlinux7/etc/redhat-release b/third_party/python/distro/tests/resources/distros/cloudlinux7/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/cloudlinux7/etc/redhat-release
+@@ -0,0 +1,1 @@
++CloudLinux release 7.3 (Yury Malyshev)
+diff --git a/third_party/python/distro/tests/resources/distros/coreos/etc/oem-release b/third_party/python/distro/tests/resources/distros/coreos/etc/oem-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/coreos/etc/oem-release
+@@ -0,0 +1,5 @@
++ID=digitalocean
++VERSION_ID=0.0.4
++NAME="DigitalOcean"
++HOME_URL="https://www.digitalocean.com/"
++BUG_REPORT_URL="https://github.com/coreos/bugs/issues"
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/coreos/etc/os-release b/third_party/python/distro/tests/resources/distros/coreos/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/coreos/etc/os-release
+@@ -0,0 +1,9 @@
++NAME=CoreOS
++ID=coreos
++VERSION=899.15.0
++VERSION_ID=899.15.0
++BUILD_ID=2016-04-05-1035
++PRETTY_NAME="CoreOS 899.15.0"
++ANSI_COLOR="1;32"
++HOME_URL="https://coreos.com/"
++BUG_REPORT_URL="https://github.com/coreos/bugs/issues"
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/debian8/bin/lsb_release b/third_party/python/distro/tests/resources/distros/debian8/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/debian8/bin/lsb_release
+@@ -0,0 +1,21 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command works without a corresponding
++# etc/lsb-release file.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++echo "No LSB modules are available."
++echo "Distributor ID: Debian"
++echo "Description: Debian GNU/Linux 8.2 (jessie)"
++echo "Release: 8.2"
++echo "Codename: jessie"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/debian8/etc/debian_version b/third_party/python/distro/tests/resources/distros/debian8/etc/debian_version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/debian8/etc/debian_version
+@@ -0,0 +1,1 @@
++8.2
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/debian8/etc/os-release b/third_party/python/distro/tests/resources/distros/debian8/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/debian8/etc/os-release
+@@ -0,0 +1,8 @@
++PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
++NAME="Debian GNU/Linux"
++VERSION_ID="8"
++VERSION="8 (jessie)"
++ID=debian
++HOME_URL="http://www.debian.org/"
++SUPPORT_URL="http://www.debian.org/support/"
++BUG_REPORT_URL="https://bugs.debian.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/exherbo/etc/os-release b/third_party/python/distro/tests/resources/distros/exherbo/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/exherbo/etc/os-release
+@@ -0,0 +1,7 @@
++NAME="Exherbo"
++PRETTY_NAME="Exherbo Linux"
++ID="exherbo"
++ANSI_COLOR="0;32"
++HOME_URL="https://www.exherbo.org/"
++SUPPORT_URL="irc://irc.freenode.net/#exherbo"
++BUG_REPORT_URL="https://bugs.exherbo.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/fedora-release b/third_party/python/distro/tests/resources/distros/fedora19/etc/fedora-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/fedora-release
+@@ -0,0 +1,1 @@
++Fedora release 19 (SchrΓΆdinger’s Cat)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/issue b/third_party/python/distro/tests/resources/distros/fedora19/etc/issue
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/issue
+@@ -0,0 +1,3 @@
++Fedora release 19 (SchrΓΆdinger’s Cat)
++Kernel \r on an \m (\l)
++
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/issue.net b/third_party/python/distro/tests/resources/distros/fedora19/etc/issue.net
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/issue.net
+@@ -0,0 +1,2 @@
++Fedora release 19 (SchrΓΆdinger’s Cat)
++Kernel \r on an \m (\l)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/os-release b/third_party/python/distro/tests/resources/distros/fedora19/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/os-release
+@@ -0,0 +1,7 @@
++NAME=Fedora
++VERSION="19 (SchrΓΆdinger’s Cat)"
++ID=fedora
++VERSION_ID=19
++PRETTY_NAME="Fedora 19 (SchrΓΆdinger’s Cat)"
++ANSI_COLOR="0;34"
++CPE_NAME="cpe:/o:fedoraproject:fedora:19"
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/redhat-release b/third_party/python/distro/tests/resources/distros/fedora19/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/redhat-release
+@@ -0,0 +1,1 @@
++Fedora release 19 (SchrΓΆdinger’s Cat)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/system-release b/third_party/python/distro/tests/resources/distros/fedora19/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/system-release
+@@ -0,0 +1,1 @@
++Fedora release 19 (SchrΓΆdinger’s Cat)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora19/etc/system-release-cpe b/third_party/python/distro/tests/resources/distros/fedora19/etc/system-release-cpe
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora19/etc/system-release-cpe
+@@ -0,0 +1,1 @@
++cpe:/o:fedoraproject:fedora:19
+diff --git a/third_party/python/distro/tests/resources/distros/fedora23/etc/fedora-release b/third_party/python/distro/tests/resources/distros/fedora23/etc/fedora-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora23/etc/fedora-release
+@@ -0,0 +1,1 @@
++Fedora release 23 (Twenty Three)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora23/etc/os-release b/third_party/python/distro/tests/resources/distros/fedora23/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora23/etc/os-release
+@@ -0,0 +1,14 @@
++NAME=Fedora
++VERSION="23 (Twenty Three)"
++ID=fedora
++VERSION_ID=23
++PRETTY_NAME="Fedora 23 (Twenty Three)"
++ANSI_COLOR="0;34"
++CPE_NAME="cpe:/o:fedoraproject:fedora:23"
++HOME_URL="https://fedoraproject.org/"
++BUG_REPORT_URL="https://bugzilla.redhat.com/"
++REDHAT_BUGZILLA_PRODUCT="Fedora"
++REDHAT_BUGZILLA_PRODUCT_VERSION=23
++REDHAT_SUPPORT_PRODUCT="Fedora"
++REDHAT_SUPPORT_PRODUCT_VERSION=23
++PRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy
+diff --git a/third_party/python/distro/tests/resources/distros/fedora23/etc/redhat-release b/third_party/python/distro/tests/resources/distros/fedora23/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora23/etc/redhat-release
+@@ -0,0 +1,1 @@
++Fedora release 23 (Twenty Three)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora23/etc/system-release b/third_party/python/distro/tests/resources/distros/fedora23/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora23/etc/system-release
+@@ -0,0 +1,1 @@
++Fedora release 23 (Twenty Three)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora23/usr/lib/os-release b/third_party/python/distro/tests/resources/distros/fedora23/usr/lib/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora23/usr/lib/os-release
+@@ -0,0 +1,14 @@
++NAME=Fedora
++VERSION="23 (Twenty Three)"
++ID=fedora
++VERSION_ID=23
++PRETTY_NAME="Fedora 23 (Twenty Three)"
++ANSI_COLOR="0;34"
++CPE_NAME="cpe:/o:fedoraproject:fedora:23"
++HOME_URL="https://fedoraproject.org/"
++BUG_REPORT_URL="https://bugzilla.redhat.com/"
++REDHAT_BUGZILLA_PRODUCT="Fedora"
++REDHAT_BUGZILLA_PRODUCT_VERSION=23
++REDHAT_SUPPORT_PRODUCT="Fedora"
++REDHAT_SUPPORT_PRODUCT_VERSION=23
++PRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy
+diff --git a/third_party/python/distro/tests/resources/distros/fedora30/etc/fedora-release b/third_party/python/distro/tests/resources/distros/fedora30/etc/fedora-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora30/etc/fedora-release
+@@ -0,0 +1,1 @@
++Fedora release 30 (Thirty)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora30/etc/os-release b/third_party/python/distro/tests/resources/distros/fedora30/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora30/etc/os-release
+@@ -0,0 +1,19 @@
++NAME=Fedora
++VERSION="30 (Thirty)"
++ID=fedora
++VERSION_ID=30
++VERSION_CODENAME=""
++PLATFORM_ID="platform:f30"
++PRETTY_NAME="Fedora 30 (Thirty)"
++ANSI_COLOR="0;34"
++LOGO=fedora-logo-icon
++CPE_NAME="cpe:/o:fedoraproject:fedora:30"
++HOME_URL="https://fedoraproject.org/"
++DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/30/system-administrators-guide/"
++SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"
++BUG_REPORT_URL="https://bugzilla.redhat.com/"
++REDHAT_BUGZILLA_PRODUCT="Fedora"
++REDHAT_BUGZILLA_PRODUCT_VERSION=30
++REDHAT_SUPPORT_PRODUCT="Fedora"
++REDHAT_SUPPORT_PRODUCT_VERSION=30
++PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
+diff --git a/third_party/python/distro/tests/resources/distros/fedora30/etc/redhat-release b/third_party/python/distro/tests/resources/distros/fedora30/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora30/etc/redhat-release
+@@ -0,0 +1,1 @@
++Fedora release 30 (Thirty)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora30/etc/system-release b/third_party/python/distro/tests/resources/distros/fedora30/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora30/etc/system-release
+@@ -0,0 +1,1 @@
++Fedora release 30 (Thirty)
+diff --git a/third_party/python/distro/tests/resources/distros/fedora30/usr/lib/os-release b/third_party/python/distro/tests/resources/distros/fedora30/usr/lib/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/fedora30/usr/lib/os-release
+@@ -0,0 +1,19 @@
++NAME=Fedora
++VERSION="30 (Thirty)"
++ID=fedora
++VERSION_ID=30
++VERSION_CODENAME=""
++PLATFORM_ID="platform:f30"
++PRETTY_NAME="Fedora 30 (Thirty)"
++ANSI_COLOR="0;34"
++LOGO=fedora-logo-icon
++CPE_NAME="cpe:/o:fedoraproject:fedora:30"
++HOME_URL="https://fedoraproject.org/"
++DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/30/system-administrators-guide/"
++SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"
++BUG_REPORT_URL="https://bugzilla.redhat.com/"
++REDHAT_BUGZILLA_PRODUCT="Fedora"
++REDHAT_BUGZILLA_PRODUCT_VERSION=30
++REDHAT_SUPPORT_PRODUCT="Fedora"
++REDHAT_SUPPORT_PRODUCT_VERSION=30
++PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
+diff --git a/third_party/python/distro/tests/resources/distros/freebsd111/bin/uname b/third_party/python/distro/tests/resources/distros/freebsd111/bin/uname
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/freebsd111/bin/uname
+@@ -0,0 +1,4 @@
++#!/bin/sh
++
++echo "FreeBSD 11.1-RELEASE"
++
+diff --git a/third_party/python/distro/tests/resources/distros/gentoo/etc/gentoo-release b/third_party/python/distro/tests/resources/distros/gentoo/etc/gentoo-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/gentoo/etc/gentoo-release
+@@ -0,0 +1,1 @@
++Gentoo Base System release 2.2
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/gentoo/etc/os-release b/third_party/python/distro/tests/resources/distros/gentoo/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/gentoo/etc/os-release
+@@ -0,0 +1,7 @@
++NAME=Gentoo
++ID=gentoo
++PRETTY_NAME="Gentoo/Linux"
++ANSI_COLOR="1;32"
++HOME_URL="http://www.gentoo.org/"
++SUPPORT_URL="http://www.gentoo.org/main/en/support.xml"
++BUG_REPORT_URL="https://bugs.gentoo.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/kali/etc/os-release b/third_party/python/distro/tests/resources/distros/kali/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/kali/etc/os-release
+@@ -0,0 +1,10 @@
++PRETTY_NAME="Kali GNU/Linux Rolling"
++NAME="Kali GNU/Linux"
++ID=kali
++VERSION="2017.1"
++VERSION_ID="2017.1"
++ID_LIKE=debian
++ANSI_COLOR="1;31"
++HOME_URL="http://www.kali.org/"
++SUPPORT_URL="http://forums.kali.org/"
++BUG_REPORT_URL="http://bugs.kali.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/kvmibm1/bin/lsb_release b/third_party/python/distro/tests/resources/distros/kvmibm1/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/kvmibm1/bin/lsb_release
+@@ -0,0 +1,21 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command works without a corresponding
++# etc/lsb-release file.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++echo "LSB Version: :core-4.1-noarch:core-4.1-s390x"
++echo "Distributor ID: kvmibm"
++echo "Description: KVM for IBM z Systems release 1.1.1 (Z) "
++echo "Release: 1.1.1"
++echo "Codename: Z"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/kvmibm1/etc/base-release b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/base-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/base-release
+@@ -0,0 +1,1 @@
++KVM for IBM z Systems release 1.1.1 (Z)
+diff --git a/third_party/python/distro/tests/resources/distros/kvmibm1/etc/os-release b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/os-release
+@@ -0,0 +1,9 @@
++NAME="KVM for IBM z Systems"
++VERSION="1.1.1 (Z)"
++ID="kvmibm"
++ID_LIKE="rhel fedora"
++VERSION_ID="1.1.1"
++PRETTY_NAME="KVM for IBM z Systems 1.1.1 (Z)"
++ANSI_COLOR="0;34"
++CPE_NAME="cpe:/o:ibm:kvmibm:1.1.1"
++BUILD_ID="20160316"
+diff --git a/third_party/python/distro/tests/resources/distros/kvmibm1/etc/redhat-release b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/redhat-release
+@@ -0,0 +1,1 @@
++KVM for IBM z Systems release 1.1.1 (Z)
+diff --git a/third_party/python/distro/tests/resources/distros/kvmibm1/etc/system-release b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/kvmibm1/etc/system-release
+@@ -0,0 +1,1 @@
++KVM for IBM z Systems release 1.1.1 (Z)
+diff --git a/third_party/python/distro/tests/resources/distros/linuxmint17/bin/lsb_release b/third_party/python/distro/tests/resources/distros/linuxmint17/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/linuxmint17/bin/lsb_release
+@@ -0,0 +1,43 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++if [[ -n $LSB_VERSION ]]; then
++ echo "LSB Version: $LSB_VERSION"
++else
++ echo "No LSB modules are available."
++fi
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/linuxmint17/etc/debian_version b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/debian_version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/debian_version
+@@ -0,0 +1,1 @@
++jessie/sid
+diff --git a/third_party/python/distro/tests/resources/distros/linuxmint17/etc/lsb-release b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/lsb-release
+@@ -0,0 +1,4 @@
++DISTRIB_ID=LinuxMint
++DISTRIB_RELEASE=17.3
++DISTRIB_CODENAME=rosa
++DISTRIB_DESCRIPTION="Linux Mint 17.3 Rosa"
+diff --git a/third_party/python/distro/tests/resources/distros/linuxmint17/etc/os-release b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/os-release
+@@ -0,0 +1,9 @@
++NAME="Ubuntu"
++VERSION="14.04.3 LTS, Trusty Tahr"
++ID=ubuntu
++ID_LIKE=debian
++PRETTY_NAME="Ubuntu 14.04.3 LTS"
++VERSION_ID="14.04"
++HOME_URL="http://www.ubuntu.com/"
++SUPPORT_URL="http://help.ubuntu.com/"
++BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
+diff --git a/third_party/python/distro/tests/resources/distros/linuxmint17/etc/upstream-release/lsb-release b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/upstream-release/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/linuxmint17/etc/upstream-release/lsb-release
+@@ -0,0 +1,4 @@
++DISTRIB_ID=Ubuntu
++DISTRIB_RELEASE=14.04
++DISTRIB_CODENAME=trusty
++DISTRIB_DESCRIPTION="Ubuntu 14.04 LTS"
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/bin/lsb_release b/third_party/python/distro/tests/resources/distros/mageia5/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/bin/lsb_release
+@@ -0,0 +1,39 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++echo "LSB Version: ${LSB_VERSION:-*}"
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/lsb-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/lsb-release
+@@ -0,0 +1,5 @@
++LSB_VERSION=
++DISTRIB_ID="Mageia"
++DISTRIB_RELEASE=5
++DISTRIB_CODENAME=thornicroft
++DISTRIB_DESCRIPTION="Mageia 5"
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/mageia-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/mageia-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/mageia-release
+@@ -0,0 +1,1 @@
++Mageia release 5 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/mandrake-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/mandrake-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/mandrake-release
+@@ -0,0 +1,1 @@
++Mageia release 5 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/mandrakelinux-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/mandrakelinux-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/mandrakelinux-release
+@@ -0,0 +1,1 @@
++Mageia release 5 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/mandriva-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/mandriva-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/mandriva-release
+@@ -0,0 +1,1 @@
++Mageia release 5 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/os-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/os-release
+@@ -0,0 +1,10 @@
++NAME="Mageia"
++VERSION="5"
++ID=mageia
++VERSION_ID=5
++ID_LIKE="mandriva fedora"
++PRETTY_NAME="Mageia 5"
++ANSI_COLOR="1;36"
++HOME_URL="http://www.mageia.org/"
++SUPPORT_URL="http://www.mageia.org/support/"
++BUG_REPORT_URL="https://bugs.mageia.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/redhat-release b/third_party/python/distro/tests/resources/distros/mageia5/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/redhat-release
+@@ -0,0 +1,1 @@
++Mageia release 5 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/release b/third_party/python/distro/tests/resources/distros/mageia5/etc/release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/release
+@@ -0,0 +1,1 @@
++Mageia release 5 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/etc/version b/third_party/python/distro/tests/resources/distros/mageia5/etc/version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/etc/version
+@@ -0,0 +1,1 @@
++5 2 official
+diff --git a/third_party/python/distro/tests/resources/distros/mageia5/usr/lib/os-release b/third_party/python/distro/tests/resources/distros/mageia5/usr/lib/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mageia5/usr/lib/os-release
+@@ -0,0 +1,10 @@
++NAME="Mageia"
++VERSION="5"
++ID=mageia
++VERSION_ID=5
++ID_LIKE="mandriva fedora"
++PRETTY_NAME="Mageia 5"
++ANSI_COLOR="1;36"
++HOME_URL="http://www.mageia.org/"
++SUPPORT_URL="http://www.mageia.org/support/"
++BUG_REPORT_URL="https://bugs.mageia.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/bin/lsb_release b/third_party/python/distro/tests/resources/distros/mandriva2011/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/bin/lsb_release
+@@ -0,0 +1,39 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++echo "LSB Version: ${LSB_VERSION:-*}"
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/lsb-release b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/lsb-release
+@@ -0,0 +1,5 @@
++LSB_VERSION=
++DISTRIB_ID=MandrivaLinux
++DISTRIB_RELEASE=2011.0
++DISTRIB_CODENAME=turtle
++DISTRIB_DESCRIPTION="Mandriva Linux 2011.0"
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandrake-release b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandrake-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandrake-release
+@@ -0,0 +1,1 @@
++Mandriva Linux release 2011.0 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandrakelinux-release b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandrakelinux-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandrakelinux-release
+@@ -0,0 +1,1 @@
++Mandriva Linux release 2011.0 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandriva-release b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandriva-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/mandriva-release
+@@ -0,0 +1,1 @@
++Mandriva Linux release 2011.0 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/redhat-release b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/redhat-release
+@@ -0,0 +1,1 @@
++Mandriva Linux release 2011.0 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/release b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/release
+@@ -0,0 +1,1 @@
++Mandriva Linux release 2011.0 (Official) for x86_64
+diff --git a/third_party/python/distro/tests/resources/distros/mandriva2011/etc/version b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/mandriva2011/etc/version
+@@ -0,0 +1,1 @@
++2011.0.0 2 cooker
+diff --git a/third_party/python/distro/tests/resources/distros/manjaro1512/bin/lsb_release b/third_party/python/distro/tests/resources/distros/manjaro1512/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/manjaro1512/bin/lsb_release
+@@ -0,0 +1,43 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++if [[ -n $LSB_VERSION ]]; then
++ echo "LSB Version: $LSB_VERSION"
++else
++ echo "No LSB modules are available."
++fi
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/manjaro1512/etc/lsb-release b/third_party/python/distro/tests/resources/distros/manjaro1512/etc/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/manjaro1512/etc/lsb-release
+@@ -0,0 +1,4 @@
++DISTRIB_ID=ManjaroLinux
++DISTRIB_RELEASE=15.12
++DISTRIB_CODENAME=Capella
++DISTRIB_DESCRIPTION="Manjaro Linux"
+diff --git a/third_party/python/distro/tests/resources/distros/manjaro1512/etc/manjaro-release b/third_party/python/distro/tests/resources/distros/manjaro1512/etc/manjaro-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/manjaro1512/etc/manjaro-release
+@@ -0,0 +1,1 @@
++Manjaro Linux
+diff --git a/third_party/python/distro/tests/resources/distros/manjaro1512/etc/os-release b/third_party/python/distro/tests/resources/distros/manjaro1512/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/manjaro1512/etc/os-release
+@@ -0,0 +1,7 @@
++NAME="Manjaro Linux"
++ID=manjaro
++PRETTY_NAME="Manjaro Linux"
++ANSI_COLOR="1;32"
++HOME_URL="http://www.manjaro.org/"
++SUPPORT_URL="http://www.manjaro.org/"
++BUG_REPORT_URL="http://bugs.manjaro.org/"
+diff --git a/third_party/python/distro/tests/resources/distros/netbsd711/bin/uname b/third_party/python/distro/tests/resources/distros/netbsd711/bin/uname
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/netbsd711/bin/uname
+@@ -0,0 +1,4 @@
++#!/bin/sh
++
++echo "NetBSD 7.1.1"
++
+diff --git a/third_party/python/distro/tests/resources/distros/openbsd62/bin/uname b/third_party/python/distro/tests/resources/distros/openbsd62/bin/uname
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/openbsd62/bin/uname
+@@ -0,0 +1,4 @@
++#!/bin/sh
++
++echo "OpenBSD 6.2"
++
+diff --git a/third_party/python/distro/tests/resources/distros/openelec6/etc/os-release b/third_party/python/distro/tests/resources/distros/openelec6/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/openelec6/etc/os-release
+@@ -0,0 +1,9 @@
++NAME="OpenELEC"
++VERSION="6.0.3"
++ID="openelec"
++VERSION_ID="6.0"
++PRETTY_NAME="OpenELEC (official) - Version: 6.0.3"
++HOME_URL="http://www.openelec.tv"
++BUG_REPORT_URL="https://github.com/OpenELEC/OpenELEC.tv"
++OPENELEC_ARCH="imx6.arm"
++OPENELEC_BUILD="official"
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/opensuse42/etc/SuSE-release b/third_party/python/distro/tests/resources/distros/opensuse42/etc/SuSE-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/opensuse42/etc/SuSE-release
+@@ -0,0 +1,1 @@
++openSUSE 42.1 (x86_64)
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/opensuse42/etc/os-release b/third_party/python/distro/tests/resources/distros/opensuse42/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/opensuse42/etc/os-release
+@@ -0,0 +1,10 @@
++NAME="openSUSE Leap"
++VERSION="42.1"
++VERSION_ID="42.1"
++PRETTY_NAME="openSUSE Leap 42.1 (x86_64)"
++ID=opensuse
++ANSI_COLOR="0;32"
++CPE_NAME="cpe:/o:opensuse:opensuse:42.1"
++BUG_REPORT_URL="https://bugs.opensuse.org"
++HOME_URL="https://opensuse.org/"
++ID_LIKE="suse"
+diff --git a/third_party/python/distro/tests/resources/distros/oracle7/etc/oracle-release b/third_party/python/distro/tests/resources/distros/oracle7/etc/oracle-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/oracle7/etc/oracle-release
+@@ -0,0 +1,1 @@
++Oracle Linux Server release 7.5
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/oracle7/etc/os-release b/third_party/python/distro/tests/resources/distros/oracle7/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/oracle7/etc/os-release
+@@ -0,0 +1,14 @@
++NAME="Oracle Linux Server"
++VERSION="7.5"
++ID="ol"
++VERSION_ID="7.5"
++PRETTY_NAME="Oracle Linux Server 7.5"
++ANSI_COLOR="0;31"
++CPE_NAME="cpe:/o:oracle:linux:7:5:server"
++HOME_URL="https://linux.oracle.com/"
++BUG_REPORT_URL="https://bugzilla.oracle.com/"
++
++ORACLE_BUGZILLA_PRODUCT="Oracle Linux 7"
++ORACLE_BUGZILLA_PRODUCT_VERSION=7.5
++ORACLE_SUPPORT_PRODUCT="Oracle Linux"
++ORACLE_SUPPORT_PRODUCT_VERSION=7.5
+diff --git a/third_party/python/distro/tests/resources/distros/raspbian7/etc/debian_version b/third_party/python/distro/tests/resources/distros/raspbian7/etc/debian_version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/raspbian7/etc/debian_version
+@@ -0,0 +1,1 @@
++7.1
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/raspbian7/etc/os-release b/third_party/python/distro/tests/resources/distros/raspbian7/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/raspbian7/etc/os-release
+@@ -0,0 +1,10 @@
++PRETTY_NAME="Raspbian GNU/Linux 7 (wheezy)"
++NAME="Raspbian GNU/Linux"
++VERSION_ID="7"
++VERSION="7 (wheezy)"
++ID=raspbian
++ID_LIKE=debian
++ANSI_COLOR="1;31"
++HOME_URL="http://www.raspbian.org/"
++SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
++BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/raspbian7/etc/os-release.orig b/third_party/python/distro/tests/resources/distros/raspbian7/etc/os-release.orig
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/raspbian7/etc/os-release.orig
+@@ -0,0 +1,9 @@
++PRETTY_NAME="Debian #OSNAME# 7 (wheezy)"
++NAME="Debian #OSNAME#"
++VERSION_ID="7"
++VERSION="7 (wheezy)"
++ID=debian
++ANSI_COLOR="1;31"
++HOME_URL="http://www.debian.org/"
++SUPPORT_URL="http://www.debian.org/support/"
++BUG_REPORT_URL="http://bugs.debian.org/"
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/raspbian8/etc/debian_version b/third_party/python/distro/tests/resources/distros/raspbian8/etc/debian_version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/raspbian8/etc/debian_version
+@@ -0,0 +1,1 @@
++8.0
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/raspbian8/etc/os-release b/third_party/python/distro/tests/resources/distros/raspbian8/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/raspbian8/etc/os-release
+@@ -0,0 +1,9 @@
++PRETTY_NAME="Raspbian GNU/Linux 8 (jessie)"
++NAME="Raspbian GNU/Linux"
++VERSION_ID="8"
++VERSION="8 (jessie)"
++ID=raspbian
++ID_LIKE=debian
++HOME_URL="http://www.raspbian.org/"
++SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
++BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/rhel5/etc/redhat-release b/third_party/python/distro/tests/resources/distros/rhel5/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/rhel5/etc/redhat-release
+@@ -0,0 +1,1 @@
++Red Hat Enterprise Linux Server release 5.11 (Tikanga)
+diff --git a/third_party/python/distro/tests/resources/distros/rhel6/etc/redhat-release b/third_party/python/distro/tests/resources/distros/rhel6/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/rhel6/etc/redhat-release
+@@ -0,0 +1,1 @@
++Red Hat Enterprise Linux Server release 6.5 (Santiago)
+diff --git a/third_party/python/distro/tests/resources/distros/rhel6/etc/system-release b/third_party/python/distro/tests/resources/distros/rhel6/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/rhel6/etc/system-release
+@@ -0,0 +1,1 @@
++Red Hat Enterprise Linux Server release 6.5 (Santiago)
+diff --git a/third_party/python/distro/tests/resources/distros/rhel7/etc/os-release b/third_party/python/distro/tests/resources/distros/rhel7/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/rhel7/etc/os-release
+@@ -0,0 +1,15 @@
++NAME="Red Hat Enterprise Linux Server"
++VERSION="7.0 (Maipo)"
++ID="rhel"
++ID_LIKE="fedora"
++VERSION_ID="7.0"
++PRETTY_NAME="Red Hat Enterprise Linux Server 7.0 (Maipo)"
++ANSI_COLOR="0;31"
++CPE_NAME="cpe:/o:redhat:enterprise_linux:7.0:GA:server"
++HOME_URL="https://www.redhat.com/"
++BUG_REPORT_URL="https://bugzilla.redhat.com/"
++
++REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
++REDHAT_BUGZILLA_PRODUCT_VERSION=7.0
++REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
++REDHAT_SUPPORT_PRODUCT_VERSION=7.0
+diff --git a/third_party/python/distro/tests/resources/distros/rhel7/etc/redhat-release b/third_party/python/distro/tests/resources/distros/rhel7/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/rhel7/etc/redhat-release
+@@ -0,0 +1,1 @@
++Red Hat Enterprise Linux Server release 7.0 (Maipo)
+diff --git a/third_party/python/distro/tests/resources/distros/rhel7/etc/system-release b/third_party/python/distro/tests/resources/distros/rhel7/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/rhel7/etc/system-release
+@@ -0,0 +1,1 @@
++Red Hat Enterprise Linux Server release 7.0 (Maipo)
+diff --git a/third_party/python/distro/tests/resources/distros/scientific6/etc/redhat-release b/third_party/python/distro/tests/resources/distros/scientific6/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/scientific6/etc/redhat-release
+@@ -0,0 +1,1 @@
++Scientific Linux release 6.4 (Carbon)
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/scientific6/etc/system-release b/third_party/python/distro/tests/resources/distros/scientific6/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/scientific6/etc/system-release
+@@ -0,0 +1,1 @@
++Scientific Linux release 6.4 (Carbon)
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/scientific7/etc/os-release b/third_party/python/distro/tests/resources/distros/scientific7/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/scientific7/etc/os-release
+@@ -0,0 +1,15 @@
++NAME="Scientific Linux"
++VERSION="7.2 (Nitrogen)"
++ID="rhel"
++ID_LIKE="fedora"
++VERSION_ID="7.2"
++PRETTY_NAME="Scientific Linux 7.2 (Nitrogen)"
++ANSI_COLOR="0;31"
++CPE_NAME="cpe:/o:scientificlinux:scientificlinux:7.2:GA"
++HOME_URL="http://www.scientificlinux.org//"
++BUG_REPORT_URL="mailto:scientific-linux-devel@listserv.fnal.gov"
++
++REDHAT_BUGZILLA_PRODUCT="Scientific Linux 7"
++REDHAT_BUGZILLA_PRODUCT_VERSION=7.2
++REDHAT_SUPPORT_PRODUCT="Scientific Linux"
++REDHAT_SUPPORT_PRODUCT_VERSION="7.2"
+diff --git a/third_party/python/distro/tests/resources/distros/scientific7/etc/redhat-release b/third_party/python/distro/tests/resources/distros/scientific7/etc/redhat-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/scientific7/etc/redhat-release
+@@ -0,0 +1,1 @@
++Scientific Linux release 7.2 (Nitrogen)
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/scientific7/etc/sl-release b/third_party/python/distro/tests/resources/distros/scientific7/etc/sl-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/scientific7/etc/sl-release
+@@ -0,0 +1,1 @@
++Scientific Linux release 7.2 (Nitrogen)
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/scientific7/etc/system-release b/third_party/python/distro/tests/resources/distros/scientific7/etc/system-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/scientific7/etc/system-release
+@@ -0,0 +1,1 @@
++Scientific Linux release 7.2 (Nitrogen)
+\ No newline at end of file
+diff --git a/third_party/python/distro/tests/resources/distros/slackware14/etc/os-release b/third_party/python/distro/tests/resources/distros/slackware14/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/slackware14/etc/os-release
+@@ -0,0 +1,10 @@
++NAME=Slackware
++VERSION="14.1"
++ID=slackware
++VERSION_ID=14.1
++PRETTY_NAME="Slackware 14.1"
++ANSI_COLOR="0;34"
++CPE_NAME="cpe:/o:slackware:slackware_linux:14.1"
++HOME_URL="http://slackware.com/"
++SUPPORT_URL="http://www.linuxquestions.org/questions/slackware-14/"
++BUG_REPORT_URL="http://www.linuxquestions.org/questions/slackware-14/"
+diff --git a/third_party/python/distro/tests/resources/distros/slackware14/etc/slackware-version b/third_party/python/distro/tests/resources/distros/slackware14/etc/slackware-version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/slackware14/etc/slackware-version
+@@ -0,0 +1,1 @@
++Slackware 14.1
+diff --git a/third_party/python/distro/tests/resources/distros/sles12/bin/lsb_release b/third_party/python/distro/tests/resources/distros/sles12/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/sles12/bin/lsb_release
+@@ -0,0 +1,21 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command works without a corresponding
++# etc/lsb-release file.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++echo "LSB Version: n/a"
++echo "Distributor ID: SUSE LINUX"
++echo "Description: SUSE Linux Enterprise Server 12 SP1"
++echo "Release: 12.1"
++echo "Codename: n/a"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/sles12/etc/SuSE-release b/third_party/python/distro/tests/resources/distros/sles12/etc/SuSE-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/sles12/etc/SuSE-release
+@@ -0,0 +1,5 @@
++SUSE Linux Enterprise Server 12 (s390x)
++VERSION = 12
++PATCHLEVEL = 1
++# This file is deprecated and will be removed in a future service pack or release.
++# Please check /etc/os-release for details about this release.
+diff --git a/third_party/python/distro/tests/resources/distros/sles12/etc/os-release b/third_party/python/distro/tests/resources/distros/sles12/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/sles12/etc/os-release
+@@ -0,0 +1,7 @@
++NAME="SLES"
++VERSION="12-SP1"
++VERSION_ID="12.1"
++PRETTY_NAME="SUSE Linux Enterprise Server 12 SP1"
++ID="sles"
++ANSI_COLOR="0;32"
++CPE_NAME="cpe:/o:suse:sles:12:sp1"
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu14/bin/lsb_release b/third_party/python/distro/tests/resources/distros/ubuntu14/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu14/bin/lsb_release
+@@ -0,0 +1,39 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++echo "No LSB modules are available."
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu14/etc/debian_version b/third_party/python/distro/tests/resources/distros/ubuntu14/etc/debian_version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu14/etc/debian_version
+@@ -0,0 +1,1 @@
++jessie/sid
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu14/etc/lsb-release b/third_party/python/distro/tests/resources/distros/ubuntu14/etc/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu14/etc/lsb-release
+@@ -0,0 +1,4 @@
++DISTRIB_ID=Ubuntu
++DISTRIB_RELEASE=14.04
++DISTRIB_CODENAME=trusty
++DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu14/etc/os-release b/third_party/python/distro/tests/resources/distros/ubuntu14/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu14/etc/os-release
+@@ -0,0 +1,9 @@
++NAME="Ubuntu"
++VERSION="14.04.3 LTS, Trusty Tahr"
++ID=ubuntu
++ID_LIKE=debian
++PRETTY_NAME="Ubuntu 14.04.3 LTS"
++VERSION_ID="14.04"
++HOME_URL="http://www.ubuntu.com/"
++SUPPORT_URL="http://help.ubuntu.com/"
++BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu16/bin/lsb_release b/third_party/python/distro/tests/resources/distros/ubuntu16/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu16/bin/lsb_release
+@@ -0,0 +1,39 @@
++#!/bin/bash
++#
++# lsb_release command for testing the ld module.
++# Only the -a option is supported.
++#
++# This version of the lsb_release command reads an lsb-release file.
++#
++# The lsb-release file has the usual format, e.g.:
++# DISTRIB_ID=Ubuntu
++# DISTRIB_RELEASE=14.04
++# DISTRIB_CODENAME=trusty
++# DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
++# Where each line is optional. If a line is missing, the default value
++# will be the empty string.
++#
++
++if [[ "$@" != "-a" ]]; then
++ echo "Usage: lsb_release -a"
++ exit 2
++fi
++
++# Because the PATH is set to just this directory, we cannot use 'dirname'
++# or other external programs, but need to use built-in abilities of bash.
++LSB_FILE="${0%/*}/../etc/lsb-release"
++
++if [[ ! -f $LSB_FILE ]]; then
++ echo "Error: LSB release file does not exist: $LSB_FILE"
++ exit 1
++fi
++
++source $LSB_FILE
++
++echo "No LSB modules are available."
++echo "Distributor ID: ${DISTRIB_ID:-}"
++echo "Description: ${DISTRIB_DESCRIPTION:-}"
++echo "Release: ${DISTRIB_RELEASE:-}"
++echo "Codename: ${DISTRIB_CODENAME:-}"
++
++exit 0
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu16/etc/debian_version b/third_party/python/distro/tests/resources/distros/ubuntu16/etc/debian_version
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu16/etc/debian_version
+@@ -0,0 +1,1 @@
++stretch/sid
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu16/etc/lsb-release b/third_party/python/distro/tests/resources/distros/ubuntu16/etc/lsb-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu16/etc/lsb-release
+@@ -0,0 +1,4 @@
++DISTRIB_ID=Ubuntu
++DISTRIB_RELEASE=16.04
++DISTRIB_CODENAME=xenial
++DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
+diff --git a/third_party/python/distro/tests/resources/distros/ubuntu16/etc/os-release b/third_party/python/distro/tests/resources/distros/ubuntu16/etc/os-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/distros/ubuntu16/etc/os-release
+@@ -0,0 +1,10 @@
++NAME="Ubuntu"
++VERSION="16.04.1 LTS (Xenial Xerus)"
++ID=ubuntu
++ID_LIKE=debian
++PRETTY_NAME="Ubuntu 16.04.1 LTS"
++VERSION_ID="16.04"
++HOME_URL="http://www.ubuntu.com/"
++SUPPORT_URL="http://help.ubuntu.com/"
++BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
++UBUNTU_CODENAME=xenial
+diff --git a/third_party/python/distro/tests/resources/special/empty-release b/third_party/python/distro/tests/resources/special/empty-release
+new file mode 100644
+diff --git a/third_party/python/distro/tests/resources/testdistros/distro/baduname/bin/uname b/third_party/python/distro/tests/resources/testdistros/distro/baduname/bin/uname
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/distro/baduname/bin/uname
+@@ -0,0 +1,2 @@
++#!/bin/sh
++echo "I'm a bad uname file!"
+diff --git a/third_party/python/distro/tests/resources/testdistros/distro/unknowndistro/etc/unknowndistro-release b/third_party/python/distro/tests/resources/testdistros/distro/unknowndistro/etc/unknowndistro-release
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/distro/unknowndistro/etc/unknowndistro-release
+@@ -0,0 +1,1 @@
++Unknown Distro release 1.0 (Unknown Codename)
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc001/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc001/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc001/bin/lsb_release
+@@ -0,0 +1,5 @@
++#!/bin/bash
++rc=1
++msg="General error"
++echo "Test failure - exiting with $rc ($msg)"
++exit $rc
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc002/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc002/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc002/bin/lsb_release
+@@ -0,0 +1,5 @@
++#!/bin/bash
++rc=2
++msg="Misuse of shell builtins, or missing keyword or command"
++echo "Test failure - exiting with $rc ($msg)"
++exit $rc
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc126/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc126/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc126/bin/lsb_release
+@@ -0,0 +1,5 @@
++#!/bin/bash
++rc=126
++msg="Cannot execute command"
++echo "Test failure - exiting with $rc ($msg)"
++exit $rc
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc130/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc130/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc130/bin/lsb_release
+@@ -0,0 +1,5 @@
++#!/bin/bash
++rc=130
++msg="Signal 2 - Script terminated with Ctrl-C"
++echo "Test failure - exiting with $rc ($msg)"
++exit $rc
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc255/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc255/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/lsb_rc255/bin/lsb_release
+@@ -0,0 +1,5 @@
++#!/bin/bash
++rc=255
++msg="Exit code out of range"
++echo "Test failure - exiting with $rc ($msg)"
++exit $rc
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_nomodules/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_nomodules/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_nomodules/bin/lsb_release
+@@ -0,0 +1,8 @@
++#!/bin/bash
++/bin/cat <<'EOT'
++No LSB modules are available.
++Distributor ID: Ubuntu
++Description: Ubuntu 14.04.3 LTS
++Release: 14.04
++Codename: trusty
++EOT
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_normal/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_normal/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_normal/bin/lsb_release
+@@ -0,0 +1,7 @@
++#!/bin/bash
++/bin/cat <<'EOT'
++Distributor ID: Ubuntu
++Description: Ubuntu 14.04.3 LTS
++Release: 14.04
++Codename: trusty
++EOT
+diff --git a/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_trailingblanks/bin/lsb_release b/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_trailingblanks/bin/lsb_release
+new file mode 100755
+--- /dev/null
++++ b/third_party/python/distro/tests/resources/testdistros/lsb/ubuntu14_trailingblanks/bin/lsb_release
+@@ -0,0 +1,8 @@
++#!/bin/bash
++/bin/cat <<'EOT'
++No LSB modules are available.
++Distributor ID: Ubuntu
++Description: Ubuntu 14.04.3 LTS
++Release: 14.04
++Codename: trusty
++EOT
+diff --git a/third_party/python/distro/tests/test_distro.py b/third_party/python/distro/tests/test_distro.py
+new file mode 100644
+--- /dev/null
++++ b/third_party/python/distro/tests/test_distro.py
+@@ -0,0 +1,2062 @@
++# Copyright 2015,2016 Nir Cohen
++#
++# Licensed under the Apache License, Version 2.0 (the "License");
++# you may not use this file except in compliance with the License.
++# You may obtain a copy of the License at
++#
++# http://www.apache.org/licenses/LICENSE-2.0
++#
++# Unless required by applicable law or agreed to in writing, software
++# distributed under the License is distributed on an "AS IS" BASIS,
++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++# See the License for the specific language governing permissions and
++# limitations under the License.
++
++import os
++import sys
++import ast
++import subprocess
++try:
++ from StringIO import StringIO # Python 2.x
++except ImportError:
++ from io import StringIO # Python 3.x
++
++import pytest
++
++
++BASE = os.path.abspath(os.path.dirname(__file__))
++RESOURCES = os.path.join(BASE, 'resources')
++DISTROS_DIR = os.path.join(RESOURCES, 'distros')
++TESTDISTROS = os.path.join(RESOURCES, 'testdistros')
++SPECIAL = os.path.join(RESOURCES, 'special')
++DISTROS = [dist for dist in os.listdir(DISTROS_DIR) if dist != '__shared__']
++
++
++IS_LINUX = sys.platform.startswith('linux')
++if IS_LINUX:
++ import distro
++
++ RELATIVE_UNIXCONFDIR = distro._UNIXCONFDIR[1:]
++ MODULE_DISTRO = distro._distro
++
++
++class TestNonLinuxPlatform:
++ """Obviously, this only tests Windows. Will add OS X tests on Travis
++ Later
++ """
++
++ def test_cant_use_on_windows(self):
++ try:
++ import distro # NOQA
++ except ImportError as ex:
++ assert 'Unsupported platform' in str(ex)
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestCli:
++
++ def _parse(self, command):
++ sys.argv = command.split()
++ distro.main()
++
++ def _run(self, command):
++ stdout, _ = subprocess.Popen(
++ command,
++ stdout=subprocess.PIPE,
++ stderr=subprocess.PIPE).communicate()
++ # Need to decode or we get bytes in Python 3.x
++ return stdout.decode('utf-8')
++
++ def test_cli_for_coverage_yuch(self):
++ self._parse('distro')
++ self._parse('distro -j')
++
++ def test_cli(self):
++ command = [sys.executable, '-m', 'distro']
++ desired_output = 'Name: ' + distro.name(pretty=True)
++ distro_version = distro.version(pretty=True)
++ distro_codename = distro.codename()
++ desired_output += '\n' + 'Version: ' + distro_version
++ desired_output += '\n' + 'Codename: ' + distro_codename
++ desired_output += '\n'
++ assert self._run(command) == desired_output
++
++ def test_cli_json(self):
++ command = [sys.executable, '-m', 'distro', '-j']
++ assert ast.literal_eval(self._run(command)) == distro.info()
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class DistroTestCase(object):
++ """A base class for any testcase classes that test the distributions
++ represented in the `DISTROS` subtree.
++ """
++
++ def setup_method(self, test_method):
++ # The environment stays the same across all testcases, so we
++ # save and restore the PATH env var in each test case that
++ # changes it:
++ self._saved_path = os.environ["PATH"]
++ self._saved_UNIXCONFDIR = distro._UNIXCONFDIR
++
++ def teardown_method(self, test_method):
++ os.environ["PATH"] = self._saved_path
++ distro._UNIXCONFDIR = self._saved_UNIXCONFDIR
++
++ def _setup_for_distro(self, distro_root):
++ distro_bin = os.path.join(distro_root, 'bin')
++ # We don't want to pick up a possibly present lsb_release in the
++ # distro that runs this test, so we use a PATH with only one entry:
++ os.environ["PATH"] = distro_bin
++ distro._UNIXCONFDIR = os.path.join(distro_root, RELATIVE_UNIXCONFDIR)
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestOSRelease:
++
++ def setup_method(self, test_method):
++ dist = test_method.__name__.split('_')[1]
++ os_release = os.path.join(DISTROS_DIR, dist, 'etc', 'os-release')
++ self.distro = distro.LinuxDistribution(False, os_release, 'non')
++
++ def _test_outcome(self, outcome):
++ assert self.distro.id() == outcome.get('id', '')
++ assert self.distro.name() == outcome.get('name', '')
++ assert self.distro.name(pretty=True) == outcome.get('pretty_name', '')
++ assert self.distro.version() == outcome.get('version', '')
++ assert self.distro.version(pretty=True) == \
++ outcome.get('pretty_version', '')
++ assert self.distro.version(best=True) == \
++ outcome.get('best_version', '')
++ assert self.distro.like() == outcome.get('like', '')
++ assert self.distro.codename() == outcome.get('codename', '')
++
++ def test_arch_os_release(self):
++ desired_outcome = {
++ 'id': 'arch',
++ 'name': 'Arch Linux',
++ 'pretty_name': 'Arch Linux',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_kali_os_release(self):
++ desired_outcome = {
++ 'id': 'kali',
++ 'name': 'Kali GNU/Linux',
++ 'pretty_name': 'Kali GNU/Linux Rolling',
++ 'version': '2017.1',
++ 'pretty_version': '2017.1',
++ 'best_version': '2017.1',
++ 'like': 'debian'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_centos7_os_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS Linux',
++ 'pretty_name': 'CentOS Linux 7 (Core)',
++ 'version': '7',
++ 'pretty_version': '7 (Core)',
++ 'best_version': '7',
++ 'like': 'rhel fedora',
++ 'codename': 'Core'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_coreos_os_release(self):
++ desired_outcome = {
++ 'id': 'coreos',
++ 'name': 'CoreOS',
++ 'pretty_name': 'CoreOS 899.15.0',
++ 'version': '899.15.0',
++ 'pretty_version': '899.15.0',
++ 'best_version': '899.15.0'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_debian8_os_release(self):
++ desired_outcome = {
++ 'id': 'debian',
++ 'name': 'Debian GNU/Linux',
++ 'pretty_name': 'Debian GNU/Linux 8 (jessie)',
++ 'version': '8',
++ 'pretty_version': '8 (jessie)',
++ 'best_version': '8',
++ 'codename': 'jessie'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_fedora19_os_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': u'Fedora 19 (Schr\u00F6dinger\u2019s Cat)',
++ 'version': '19',
++ 'pretty_version': u'19 (Schr\u00F6dinger\u2019s Cat)',
++ 'best_version': '19',
++ 'codename': u'Schr\u00F6dinger\u2019s Cat'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_fedora23_os_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': 'Fedora 23 (Twenty Three)',
++ 'version': '23',
++ 'pretty_version': '23 (Twenty Three)',
++ 'best_version': '23',
++ 'codename': 'Twenty Three'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_fedora30_os_release(self):
++ # Fedora 21 and above no longer have code names but the metadata in os-release was only
++ # changed in a detectable way in Fedora 30+. The piece in parenthesis in the pretty_name
++ # field contains the VARIANT and differs depending on the variant which was installed.
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': 'Fedora 30 (Thirty)',
++ 'version': '30',
++ 'pretty_version': '30',
++ 'best_version': '30',
++ 'codename': ''
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_kvmibm1_os_release(self):
++ desired_outcome = {
++ 'id': 'kvmibm',
++ 'name': 'KVM for IBM z Systems',
++ 'pretty_name': 'KVM for IBM z Systems 1.1.1 (Z)',
++ 'version': '1.1.1',
++ 'pretty_version': '1.1.1 (Z)',
++ 'best_version': '1.1.1',
++ 'like': 'rhel fedora',
++ 'codename': 'Z'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_linuxmint17_os_release(self):
++ # Note: LinuxMint 17 actually *does* have Ubuntu 14.04 data in its
++ # os-release file. See discussion in GitHub issue #78.
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (Trusty Tahr)',
++ 'best_version': '14.04.3',
++ 'like': 'debian',
++ 'codename': 'Trusty Tahr'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_mageia5_os_release(self):
++ desired_outcome = {
++ 'id': 'mageia',
++ 'name': 'Mageia',
++ 'pretty_name': 'Mageia 5',
++ 'version': '5',
++ 'pretty_version': '5',
++ 'best_version': '5',
++ 'like': 'mandriva fedora',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_manjaro1512_os_release(self):
++ self._test_outcome({
++ 'id': 'manjaro',
++ 'name': 'Manjaro Linux',
++ 'pretty_name': 'Manjaro Linux',
++ })
++
++ def test_opensuse42_os_release(self):
++ desired_outcome = {
++ 'id': 'opensuse',
++ 'name': 'openSUSE Leap',
++ 'pretty_name': 'openSUSE Leap 42.1 (x86_64)',
++ 'version': '42.1',
++ 'pretty_version': '42.1',
++ 'best_version': '42.1',
++ 'like': 'suse',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_raspbian7_os_release(self):
++ desired_outcome = {
++ 'id': 'raspbian',
++ 'name': 'Raspbian GNU/Linux',
++ 'pretty_name': 'Raspbian GNU/Linux 7 (wheezy)',
++ 'version': '7',
++ 'pretty_version': '7 (wheezy)',
++ 'best_version': '7',
++ 'like': 'debian',
++ 'codename': 'wheezy'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_raspbian8_os_release(self):
++ desired_outcome = {
++ 'id': 'raspbian',
++ 'name': 'Raspbian GNU/Linux',
++ 'pretty_name': 'Raspbian GNU/Linux 8 (jessie)',
++ 'version': '8',
++ 'pretty_version': '8 (jessie)',
++ 'best_version': '8',
++ 'like': 'debian',
++ 'codename': 'jessie'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_rhel7_os_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'pretty_name': 'Red Hat Enterprise Linux Server 7.0 (Maipo)',
++ 'version': '7.0',
++ 'pretty_version': '7.0 (Maipo)',
++ 'best_version': '7.0',
++ 'like': 'fedora',
++ 'codename': 'Maipo'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_slackware14_os_release(self):
++ desired_outcome = {
++ 'id': 'slackware',
++ 'name': 'Slackware',
++ 'pretty_name': 'Slackware 14.1',
++ 'version': '14.1',
++ 'pretty_version': '14.1',
++ 'best_version': '14.1'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_sles12_os_release(self):
++ desired_outcome = {
++ 'id': 'sles',
++ 'name': 'SLES',
++ 'pretty_name': 'SUSE Linux Enterprise Server 12 SP1',
++ 'version': '12.1',
++ 'pretty_version': '12.1',
++ 'best_version': '12.1'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_ubuntu14_os_release(self):
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (Trusty Tahr)',
++ 'best_version': '14.04.3',
++ 'like': 'debian',
++ 'codename': 'Trusty Tahr'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_ubuntu16_os_release(self):
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 16.04.1 LTS',
++ 'version': '16.04',
++ 'pretty_version': '16.04 (xenial)',
++ 'best_version': '16.04.1',
++ 'like': 'debian',
++ 'codename': 'xenial'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_amazon2016_os_release(self):
++ desired_outcome = {
++ 'id': 'amzn',
++ 'name': 'Amazon Linux AMI',
++ 'pretty_name': 'Amazon Linux AMI 2016.03',
++ 'version': '2016.03',
++ 'pretty_version': '2016.03',
++ 'best_version': '2016.03',
++ 'like': 'rhel fedora'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_scientific7_os_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Scientific Linux',
++ 'pretty_name': 'Scientific Linux 7.2 (Nitrogen)',
++ 'version': '7.2',
++ 'pretty_version': '7.2 (Nitrogen)',
++ 'best_version': '7.2',
++ 'like': 'fedora',
++ 'codename': 'Nitrogen'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_gentoo_os_release(self):
++ desired_outcome = {
++ 'id': 'gentoo',
++ 'name': 'Gentoo',
++ 'pretty_name': 'Gentoo/Linux',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_openelec6_os_release(self):
++ desired_outcome = {
++ 'id': 'openelec',
++ 'name': 'OpenELEC',
++ 'pretty_name': 'OpenELEC (official) - Version: 6.0.3',
++ 'version': '6.0',
++ 'pretty_version': '6.0',
++ 'best_version': '6.0.3',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_cloudlinux7_os_release(self):
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Yury Malyshev',
++ 'name': 'CloudLinux',
++ 'pretty_name': 'CloudLinux 7.3 (Yury Malyshev)',
++ 'like': 'rhel fedora centos',
++ 'version': '7.3',
++ 'pretty_version': '7.3 (Yury Malyshev)',
++ 'best_version': '7.3',
++ 'major_version': '7',
++ 'minor_version': '3'
++ }
++ self._test_outcome(desired_outcome)
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestLSBRelease(DistroTestCase):
++
++ def setup_method(self, test_method):
++ super(TestLSBRelease, self).setup_method(test_method)
++ dist = test_method.__name__.split('_')[1]
++ self._setup_for_distro(os.path.join(DISTROS_DIR, dist))
++ self.distro = distro.LinuxDistribution(True, 'non', 'non')
++
++ def _test_outcome(self, outcome):
++ assert self.distro.id() == outcome.get('id', '')
++ assert self.distro.name() == outcome.get('name', '')
++ assert self.distro.name(pretty=True) == outcome.get('pretty_name', '')
++ assert self.distro.version() == outcome.get('version', '')
++ assert self.distro.version(pretty=True) == \
++ outcome.get('pretty_version', '')
++ assert self.distro.version(best=True) == \
++ outcome.get('best_version', '')
++ assert self.distro.like() == outcome.get('like', '')
++ assert self.distro.codename() == outcome.get('codename', '')
++
++ def test_linuxmint17_lsb_release(self):
++ desired_outcome = {
++ 'id': 'linuxmint',
++ 'name': 'LinuxMint',
++ 'pretty_name': 'Linux Mint 17.3 Rosa',
++ 'version': '17.3',
++ 'pretty_version': '17.3 (rosa)',
++ 'best_version': '17.3',
++ 'codename': 'rosa'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_manjaro1512_lsb_release(self):
++ self._test_outcome({
++ 'id': 'manjarolinux',
++ 'name': 'ManjaroLinux',
++ 'pretty_name': 'Manjaro Linux',
++ 'version': '15.12',
++ 'pretty_version': '15.12 (Capella)',
++ 'best_version': '15.12',
++ 'codename': 'Capella'
++ })
++
++ # @pytest.mark.xfail
++ # def test_openelec6_lsb_release(self):
++ # # TODO: This should be fixed as part of #109 when dealing
++ # # with distro inconsistencies
++ # desired_outcome = {
++ # 'id': 'openelec',
++ # 'name': 'OpenELEC',
++ # 'pretty_name': 'OpenELEC (official) - Version: 6.0.3',
++ # 'version': '6.0.3',
++ # 'pretty_version': '6.0.3',
++ # 'best_version': '6.0.3',
++ # }
++ # self._test_outcome(desired_outcome)
++
++ def test_openbsd62_uname(self):
++ self._test_outcome({
++ 'id': 'openbsd',
++ 'name': 'OpenBSD',
++ 'version': '6.2',
++ 'pretty_name': 'OpenBSD 6.2',
++ 'pretty_version': '6.2',
++ 'best_version': '6.2'
++ })
++
++ def test_netbsd711_uname(self):
++ self._test_outcome({
++ 'id': 'netbsd',
++ 'name': 'NetBSD',
++ 'version': '7.1.1',
++ 'pretty_name': 'NetBSD 7.1.1',
++ 'pretty_version': '7.1.1',
++ 'best_version': '7.1.1'
++ })
++
++ def test_freebsd111_uname(self):
++ self._test_outcome({
++ 'id': 'freebsd',
++ 'name': 'FreeBSD',
++ 'version': '11.1',
++ 'pretty_name': 'FreeBSD 11.1',
++ 'pretty_version': '11.1',
++ 'best_version': '11.1'
++ })
++
++ def test_ubuntu14normal_lsb_release(self):
++ self._setup_for_distro(os.path.join(TESTDISTROS, 'lsb',
++ 'ubuntu14_normal'))
++
++ self.distro = distro.LinuxDistribution(True, 'non', 'non')
++
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (trusty)',
++ 'best_version': '14.04.3',
++ 'codename': 'trusty'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_ubuntu14nomodules_lsb_release(self):
++ self._setup_for_distro(os.path.join(TESTDISTROS, 'lsb',
++ 'ubuntu14_nomodules'))
++
++ self.distro = distro.LinuxDistribution(True, 'non', 'non')
++
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (trusty)',
++ 'best_version': '14.04.3',
++ 'codename': 'trusty'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_trailingblanks_lsb_release(self):
++ self._setup_for_distro(os.path.join(TESTDISTROS, 'lsb',
++ 'ubuntu14_trailingblanks'))
++
++ self.distro = distro.LinuxDistribution(True, 'non', 'non')
++
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (trusty)',
++ 'best_version': '14.04.3',
++ 'codename': 'trusty'
++ }
++ self._test_outcome(desired_outcome)
++
++ @pytest.mark.parametrize('errnum', ('001', '002', '126', '130', '255'))
++ def test_lsb_release_error_level(self, errnum):
++ self._setup_for_distro(os.path.join(
++ TESTDISTROS, 'lsb', 'lsb_rc{0}'.format(errnum)))
++ with pytest.raises(subprocess.CalledProcessError) as excinfo:
++ distro.LinuxDistribution(True, 'non', 'non')._lsb_release_info
++ assert excinfo.value.returncode == int(errnum)
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestSpecialRelease(DistroTestCase):
++ def _test_outcome(self, outcome):
++ assert self.distro.id() == outcome.get('id', '')
++ assert self.distro.name() == outcome.get('name', '')
++ assert self.distro.name(pretty=True) == outcome.get('pretty_name', '')
++ assert self.distro.version() == outcome.get('version', '')
++ assert self.distro.version(pretty=True) == \
++ outcome.get('pretty_version', '')
++ assert self.distro.version(best=True) == \
++ outcome.get('best_version', '')
++ assert self.distro.like() == outcome.get('like', '')
++ assert self.distro.codename() == outcome.get('codename', '')
++ assert self.distro.major_version() == outcome.get('major_version', '')
++ assert self.distro.minor_version() == outcome.get('minor_version', '')
++ assert self.distro.build_number() == outcome.get('build_number', '')
++
++ def test_empty_release(self):
++ distro_release = os.path.join(SPECIAL, 'empty-release')
++ self.distro = distro.LinuxDistribution(False, 'non', distro_release)
++
++ desired_outcome = {
++ 'id': 'empty'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_unknowndistro_release(self):
++ self._setup_for_distro(os.path.join(TESTDISTROS, 'distro',
++ 'unknowndistro'))
++
++ self.distro = distro.LinuxDistribution()
++
++ desired_outcome = {
++ 'id': 'unknowndistro',
++ 'name': 'Unknown Distro',
++ 'pretty_name': 'Unknown Distro 1.0 (Unknown Codename)',
++ 'version': '1.0',
++ 'pretty_version': '1.0 (Unknown Codename)',
++ 'best_version': '1.0',
++ 'codename': 'Unknown Codename',
++ 'major_version': '1',
++ 'minor_version': '0'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_bad_uname(self):
++ self._setup_for_distro(os.path.join(TESTDISTROS, 'distro',
++ 'baduname'))
++ self.distro = distro.LinuxDistribution()
++
++ assert self.distro.uname_attr('id') == ''
++ assert self.distro.uname_attr('name') == ''
++ assert self.distro.uname_attr('release') == ''
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestDistroRelease:
++
++ def _test_outcome(self,
++ outcome,
++ distro_name='',
++ version='',
++ release_file_id='',
++ release_file_suffix='release'):
++ release_file_id = release_file_id or distro_name
++ distro_release = os.path.join(
++ DISTROS_DIR, distro_name + version, 'etc', '{0}-{1}'.format(
++ release_file_id, release_file_suffix))
++ self.distro = distro.LinuxDistribution(False, 'non', distro_release)
++
++ assert self.distro.id() == outcome.get('id', '')
++ assert self.distro.name() == outcome.get('name', '')
++ assert self.distro.name(pretty=True) == outcome.get('pretty_name', '')
++ assert self.distro.version() == outcome.get('version', '')
++ assert self.distro.version(pretty=True) == \
++ outcome.get('pretty_version', '')
++ assert self.distro.version(best=True) == \
++ outcome.get('best_version', '')
++ assert self.distro.like() == outcome.get('like', '')
++ assert self.distro.codename() == outcome.get('codename', '')
++ assert self.distro.major_version() == outcome.get('major_version', '')
++ assert self.distro.minor_version() == outcome.get('minor_version', '')
++ assert self.distro.build_number() == outcome.get('build_number', '')
++
++ def test_arch_dist_release(self):
++ desired_outcome = {
++ 'id': 'arch'
++ }
++ self._test_outcome(desired_outcome, 'arch')
++
++ def test_centos5_dist_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS',
++ 'pretty_name': 'CentOS 5.11 (Final)',
++ 'version': '5.11',
++ 'pretty_version': '5.11 (Final)',
++ 'best_version': '5.11',
++ 'codename': 'Final',
++ 'major_version': '5',
++ 'minor_version': '11'
++ }
++ self._test_outcome(desired_outcome, 'centos', '5')
++
++ def test_centos7_dist_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS Linux',
++ 'pretty_name': 'CentOS Linux 7.1.1503 (Core)',
++ 'version': '7.1.1503',
++ 'pretty_version': '7.1.1503 (Core)',
++ 'best_version': '7.1.1503',
++ 'codename': 'Core',
++ 'major_version': '7',
++ 'minor_version': '1',
++ 'build_number': '1503'
++ }
++ self._test_outcome(desired_outcome, 'centos', '7')
++
++ def test_fedora19_dist_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': u'Fedora 19 (Schr\u00F6dinger\u2019s Cat)',
++ 'version': '19',
++ 'pretty_version': u'19 (Schr\u00F6dinger\u2019s Cat)',
++ 'best_version': '19',
++ 'codename': u'Schr\u00F6dinger\u2019s Cat',
++ 'major_version': '19'
++ }
++ self._test_outcome(desired_outcome, 'fedora', '19')
++
++ def test_fedora23_dist_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': 'Fedora 23 (Twenty Three)',
++ 'version': '23',
++ 'pretty_version': '23 (Twenty Three)',
++ 'best_version': '23',
++ 'codename': 'Twenty Three',
++ 'major_version': '23'
++ }
++ self._test_outcome(desired_outcome, 'fedora', '23')
++
++ def test_fedora30_dist_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': 'Fedora 30 (Thirty)',
++ 'version': '30',
++ 'pretty_version': '30 (Thirty)',
++ 'best_version': '30',
++ 'codename': 'Thirty',
++ 'major_version': '30'
++ }
++ self._test_outcome(desired_outcome, 'fedora', '30')
++
++ def test_gentoo_dist_release(self):
++ desired_outcome = {
++ 'id': 'gentoo',
++ 'name': 'Gentoo Base System',
++ 'pretty_name': 'Gentoo Base System 2.2',
++ 'version': '2.2',
++ 'pretty_version': '2.2',
++ 'best_version': '2.2',
++ 'major_version': '2',
++ 'minor_version': '2',
++ }
++ self._test_outcome(desired_outcome, 'gentoo')
++
++ def test_kvmibm1_dist_release(self):
++ desired_outcome = {
++ 'id': 'base',
++ 'name': 'KVM for IBM z Systems',
++ 'pretty_name': 'KVM for IBM z Systems 1.1.1 (Z)',
++ 'version': '1.1.1',
++ 'pretty_version': '1.1.1 (Z)',
++ 'best_version': '1.1.1',
++ 'codename': 'Z',
++ 'major_version': '1',
++ 'minor_version': '1',
++ 'build_number': '1'
++ }
++ self._test_outcome(desired_outcome, 'kvmibm', '1', 'base')
++
++ def test_mageia5_dist_release(self):
++ desired_outcome = {
++ 'id': 'mageia',
++ 'name': 'Mageia',
++ 'pretty_name': 'Mageia 5 (Official)',
++ 'version': '5',
++ 'pretty_version': '5 (Official)',
++ 'best_version': '5',
++ 'codename': 'Official',
++ 'major_version': '5'
++ }
++ self._test_outcome(desired_outcome, 'mageia', '5')
++
++ def test_manjaro1512_dist_release(self):
++ self._test_outcome({
++ 'id': 'manjaro',
++ 'name': 'Manjaro Linux',
++ 'pretty_name': 'Manjaro Linux',
++ 'version': '',
++ 'codename': ''
++ }, 'manjaro', '1512')
++
++ def test_opensuse42_dist_release(self):
++ desired_outcome = {
++ 'id': 'suse',
++ 'name': 'openSUSE',
++ 'pretty_name': 'openSUSE 42.1 (x86_64)',
++ 'version': '42.1',
++ 'pretty_version': '42.1 (x86_64)',
++ 'best_version': '42.1',
++ 'codename': 'x86_64',
++ 'major_version': '42',
++ 'minor_version': '1'
++ }
++ self._test_outcome(desired_outcome, 'opensuse', '42', 'SuSE')
++
++ def test_oracle7_dist_release(self):
++ desired_outcome = {
++ 'id': 'oracle',
++ 'name': 'Oracle Linux Server',
++ 'pretty_name': 'Oracle Linux Server 7.5',
++ 'version': '7.5',
++ 'pretty_version': '7.5',
++ 'best_version': '7.5',
++ 'major_version': '7',
++ 'minor_version': '5'
++ }
++ self._test_outcome(desired_outcome, 'oracle', '7')
++
++ def test_rhel6_dist_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'pretty_name': 'Red Hat Enterprise Linux Server 6.5 (Santiago)',
++ 'version': '6.5',
++ 'pretty_version': '6.5 (Santiago)',
++ 'best_version': '6.5',
++ 'codename': 'Santiago',
++ 'major_version': '6',
++ 'minor_version': '5'
++ }
++ self._test_outcome(desired_outcome, 'rhel', '6', 'redhat')
++
++ def test_rhel7_dist_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'pretty_name': 'Red Hat Enterprise Linux Server 7.0 (Maipo)',
++ 'version': '7.0',
++ 'pretty_version': '7.0 (Maipo)',
++ 'best_version': '7.0',
++ 'codename': 'Maipo',
++ 'major_version': '7',
++ 'minor_version': '0'
++ }
++ self._test_outcome(desired_outcome, 'rhel', '7', 'redhat')
++
++ def test_slackware14_dist_release(self):
++ desired_outcome = {
++ 'id': 'slackware',
++ 'name': 'Slackware',
++ 'pretty_name': 'Slackware 14.1',
++ 'version': '14.1',
++ 'pretty_version': '14.1',
++ 'best_version': '14.1',
++ 'major_version': '14',
++ 'minor_version': '1'
++ }
++ self._test_outcome(
++ desired_outcome,
++ 'slackware',
++ '14',
++ release_file_suffix='version')
++
++ def test_sles12_dist_release(self):
++ desired_outcome = {
++ 'id': 'suse',
++ 'name': 'SUSE Linux Enterprise Server',
++ 'pretty_name': 'SUSE Linux Enterprise Server 12 (s390x)',
++ 'version': '12',
++ 'pretty_version': '12 (s390x)',
++ 'best_version': '12',
++ 'major_version': '12',
++ 'codename': 's390x'
++ }
++ self._test_outcome(desired_outcome, 'sles', '12', 'SuSE')
++
++ def test_cloudlinux5_dist_release(self):
++ # Uses redhat-release only to get information.
++ # The id of 'rhel' can only be fixed with issue #109.
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Vladislav Volkov',
++ 'name': 'CloudLinux Server',
++ 'pretty_name': 'CloudLinux Server 5.11 (Vladislav Volkov)',
++ 'version': '5.11',
++ 'pretty_version': '5.11 (Vladislav Volkov)',
++ 'best_version': '5.11',
++ 'major_version': '5',
++ 'minor_version': '11'
++ }
++ self._test_outcome(desired_outcome, 'cloudlinux', '5', 'redhat')
++
++ def test_cloudlinux6_dist_release(self):
++ # Same as above, only has redhat-release.
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Oleg Makarov',
++ 'name': 'CloudLinux Server',
++ 'pretty_name': 'CloudLinux Server 6.8 (Oleg Makarov)',
++ 'version': '6.8',
++ 'pretty_version': '6.8 (Oleg Makarov)',
++ 'best_version': '6.8',
++ 'major_version': '6',
++ 'minor_version': '8'
++ }
++ self._test_outcome(desired_outcome, 'cloudlinux', '6', 'redhat')
++
++ def test_cloudlinux7_dist_release(self):
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Yury Malyshev',
++ 'name': 'CloudLinux',
++ 'pretty_name': 'CloudLinux 7.3 (Yury Malyshev)',
++ 'version': '7.3',
++ 'pretty_version': '7.3 (Yury Malyshev)',
++ 'best_version': '7.3',
++ 'major_version': '7',
++ 'minor_version': '3'
++ }
++ self._test_outcome(desired_outcome, 'cloudlinux', '7', 'redhat')
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestOverall(DistroTestCase):
++ """Test a LinuxDistribution object created with default arguments.
++
++ The direct accessor functions on that object are tested (e.g. `id()`); they
++ implement the precedence between the different sources of information.
++
++ In addition, because the distro release file is searched when not
++ specified, the information resulting from the distro release file is also
++ tested. The LSB and os-release sources are not tested again, because their
++ test is already done in TestLSBRelease and TestOSRelease, and their
++ algorithm does not depend on whether or not the file is specified.
++
++ TODO: This class should have testcases for all distros that are claimed
++ to be reliably maintained w.r.t. to their ID (see `id()`). Testcases for
++ the following distros are still missing:
++ * `amazon` - Amazon Linux
++ * `gentoo` - GenToo Linux
++ * `ibm_powerkvm` - IBM PowerKVM
++ * `parallels` - Parallels
++ * `pidora` - Pidora (Fedora remix for Raspberry Pi)
++ * `raspbian` - Raspbian
++ * `scientific` - Scientific Linux
++ * `xenserver` - XenServer
++ """
++
++ def setup_method(self, test_method):
++ super(TestOverall, self).setup_method(test_method)
++ dist = test_method.__name__.split('_')[1]
++ self._setup_for_distro(os.path.join(DISTROS_DIR, dist))
++ self.distro = distro.LinuxDistribution()
++
++ def _test_outcome(self, outcome):
++ assert self.distro.id() == outcome.get('id', '')
++ assert self.distro.name() == outcome.get('name', '')
++ assert self.distro.name(pretty=True) == outcome.get('pretty_name', '')
++ assert self.distro.version() == outcome.get('version', '')
++ assert self.distro.version(pretty=True) == \
++ outcome.get('pretty_version', '')
++ assert self.distro.version(best=True) == \
++ outcome.get('best_version', '')
++ assert self.distro.like() == outcome.get('like', '')
++ assert self.distro.codename() == outcome.get('codename', '')
++ assert self.distro.major_version() == outcome.get('major_version', '')
++ assert self.distro.minor_version() == outcome.get('minor_version', '')
++ assert self.distro.build_number() == outcome.get('build_number', '')
++
++ def _test_non_existing_release_file(self):
++ # Test the info from the searched distro release file
++ # does not have one.
++ assert self.distro.distro_release_file == ''
++ assert len(self.distro.distro_release_info()) == 0
++
++ def _test_release_file_info(self, filename, outcome):
++ # Test the info from the searched distro release file
++ assert os.path.basename(self.distro.distro_release_file) == filename
++ distro_info = self.distro.distro_release_info()
++ for key, value in outcome.items():
++ assert distro_info[key] == value
++ return distro_info
++
++ def test_arch_release(self):
++ desired_outcome = {
++ 'id': 'arch',
++ 'name': 'Arch Linux',
++ 'pretty_name': 'Arch Linux',
++ }
++ self._test_outcome(desired_outcome)
++
++ # Test the info from the searched distro release file
++ # Does not have one; The empty /etc/arch-release file is not
++ # considered a valid distro release file:
++ self._test_non_existing_release_file()
++
++ def test_centos5_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS',
++ 'pretty_name': 'CentOS 5.11 (Final)',
++ 'version': '5.11',
++ 'pretty_version': '5.11 (Final)',
++ 'best_version': '5.11',
++ 'codename': 'Final',
++ 'major_version': '5',
++ 'minor_version': '11'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'centos',
++ 'name': 'CentOS',
++ 'version_id': '5.11',
++ 'codename': 'Final'
++ }
++ self._test_release_file_info('centos-release', desired_info)
++
++ def test_centos7_release(self):
++ desired_outcome = {
++ 'id': 'centos',
++ 'name': 'CentOS Linux',
++ 'pretty_name': 'CentOS Linux 7 (Core)',
++ 'version': '7',
++ 'pretty_version': '7 (Core)',
++ 'best_version': '7.1.1503',
++ 'like': 'rhel fedora',
++ 'codename': 'Core',
++ 'major_version': '7'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'centos',
++ 'name': 'CentOS Linux',
++ 'version_id': '7.1.1503',
++ 'codename': 'Core'
++ }
++ self._test_release_file_info('centos-release', desired_info)
++
++ def test_coreos_release(self):
++ desired_outcome = {
++ 'id': 'coreos',
++ 'name': 'CoreOS',
++ 'pretty_name': 'CoreOS 899.15.0',
++ 'version': '899.15.0',
++ 'pretty_version': '899.15.0',
++ 'best_version': '899.15.0',
++ 'major_version': '899',
++ 'minor_version': '15',
++ 'build_number': '0'
++ }
++ self._test_outcome(desired_outcome)
++ self._test_non_existing_release_file()
++
++ def test_debian8_release(self):
++ desired_outcome = {
++ 'id': 'debian',
++ 'name': 'Debian GNU/Linux',
++ 'pretty_name': 'Debian GNU/Linux 8 (jessie)',
++ 'version': '8',
++ 'pretty_version': '8 (jessie)',
++ 'best_version': '8.2',
++ 'codename': 'jessie',
++ 'major_version': '8'
++ }
++ self._test_outcome(desired_outcome)
++ self._test_non_existing_release_file()
++
++ def test_exherbo_release(self):
++ desired_outcome = {
++ 'id': 'exherbo',
++ 'name': 'Exherbo',
++ 'pretty_name': 'Exherbo Linux',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_fedora19_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': u'Fedora 19 (Schr\u00F6dinger\u2019s Cat)',
++ 'version': '19',
++ 'pretty_version': u'19 (Schr\u00F6dinger\u2019s Cat)',
++ 'best_version': '19',
++ 'codename': u'Schr\u00F6dinger\u2019s Cat',
++ 'major_version': '19'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'version_id': '19',
++ 'codename': u'Schr\u00F6dinger\u2019s Cat'
++ }
++ self._test_release_file_info('fedora-release', desired_info)
++
++ def test_fedora23_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': 'Fedora 23 (Twenty Three)',
++ 'version': '23',
++ 'pretty_version': '23 (Twenty Three)',
++ 'best_version': '23',
++ 'codename': 'Twenty Three',
++ 'major_version': '23'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'version_id': '23',
++ 'codename': 'Twenty Three'
++ }
++ self._test_release_file_info('fedora-release', desired_info)
++
++ def test_fedora30_release(self):
++ desired_outcome = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'pretty_name': 'Fedora 30 (Thirty)',
++ 'version': '30',
++ 'pretty_version': '30',
++ 'best_version': '30',
++ 'codename': '',
++ 'major_version': '30'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'fedora',
++ 'name': 'Fedora',
++ 'version_id': '30',
++ 'codename': 'Thirty'
++ }
++ self._test_release_file_info('fedora-release', desired_info)
++
++ def test_kvmibm1_release(self):
++ desired_outcome = {
++ 'id': 'kvmibm',
++ 'name': 'KVM for IBM z Systems',
++ 'pretty_name': 'KVM for IBM z Systems 1.1.1 (Z)',
++ 'version': '1.1.1',
++ 'pretty_version': '1.1.1 (Z)',
++ 'best_version': '1.1.1',
++ 'like': 'rhel fedora',
++ 'codename': 'Z',
++ 'major_version': '1',
++ 'minor_version': '1',
++ 'build_number': '1'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'base',
++ 'name': 'KVM for IBM z Systems',
++ 'version_id': '1.1.1',
++ 'codename': 'Z'
++ }
++ self._test_release_file_info('base-release', desired_info)
++
++ def test_linuxmint17_release(self):
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (Trusty Tahr)',
++ 'best_version': '14.04.3',
++ 'like': 'debian',
++ 'codename': 'Trusty Tahr',
++ 'major_version': '14',
++ 'minor_version': '04'
++ }
++ self._test_outcome(desired_outcome)
++ self._test_non_existing_release_file()
++
++ def test_mageia5_release(self):
++ desired_outcome = {
++ 'id': 'mageia',
++ 'name': 'Mageia',
++ 'pretty_name': 'Mageia 5',
++ 'version': '5',
++ 'pretty_version': '5 (thornicroft)',
++ 'best_version': '5',
++ 'like': 'mandriva fedora',
++ # TODO: Codename differs between distro release and lsb_release.
++ 'codename': 'thornicroft',
++ 'major_version': '5'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'mageia',
++ 'name': 'Mageia',
++ 'version_id': '5',
++ 'codename': 'Official'
++ }
++ self._test_release_file_info('mageia-release', desired_info)
++
++ def test_manjaro1512_release(self):
++ self._test_outcome({
++ 'id': 'manjaro',
++ 'name': 'Manjaro Linux',
++ 'pretty_name': 'Manjaro Linux',
++ 'version': '15.12',
++ 'pretty_version': '15.12 (Capella)',
++ 'best_version': '15.12',
++ 'major_version': '15',
++ 'minor_version': '12',
++ 'codename': 'Capella'
++ })
++
++ self._test_release_file_info(
++ 'manjaro-release',
++ {'id': 'manjaro',
++ 'name': 'Manjaro Linux'})
++
++ def test_opensuse42_release(self):
++ desired_outcome = {
++ 'id': 'opensuse',
++ 'name': 'openSUSE Leap',
++ 'pretty_name': 'openSUSE Leap 42.1 (x86_64)',
++ 'version': '42.1',
++ 'pretty_version': '42.1 (x86_64)',
++ 'best_version': '42.1',
++ 'like': 'suse',
++ 'codename': 'x86_64',
++ 'major_version': '42',
++ 'minor_version': '1'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'SuSE',
++ 'name': 'openSUSE',
++ 'version_id': '42.1',
++ 'codename': 'x86_64'
++ }
++ self._test_release_file_info('SuSE-release', desired_info)
++
++ def test_oracle7_release(self):
++ desired_outcome = {
++ 'id': 'oracle',
++ 'name': 'Oracle Linux Server',
++ 'pretty_name': 'Oracle Linux Server 7.5',
++ 'version': '7.5',
++ 'pretty_version': '7.5',
++ 'best_version': '7.5',
++ 'major_version': '7',
++ 'minor_version': '5'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'oracle',
++ 'name': 'Oracle Linux Server',
++ 'version_id': '7.5',
++ }
++ distro_info = self._test_release_file_info(
++ 'oracle-release', desired_info)
++ assert 'codename' not in distro_info
++
++ def test_raspbian7_release(self):
++ desired_outcome = {
++ 'id': 'raspbian',
++ 'name': 'Raspbian GNU/Linux',
++ 'pretty_name': 'Raspbian GNU/Linux 7 (wheezy)',
++ 'version': '7',
++ 'pretty_version': '7 (wheezy)',
++ 'best_version': '7',
++ 'like': 'debian',
++ 'codename': 'wheezy',
++ 'major_version': '7',
++ }
++ self._test_outcome(desired_outcome)
++ self._test_non_existing_release_file()
++
++ def test_raspbian8_release(self):
++ desired_outcome = {
++ 'id': 'raspbian',
++ 'name': 'Raspbian GNU/Linux',
++ 'pretty_name': 'Raspbian GNU/Linux 8 (jessie)',
++ 'version': '8',
++ 'pretty_version': '8 (jessie)',
++ 'best_version': '8',
++ 'like': 'debian',
++ 'codename': 'jessie',
++ 'major_version': '8',
++ }
++ self._test_outcome(desired_outcome)
++ self._test_non_existing_release_file()
++
++ def test_rhel5_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'pretty_name': 'Red Hat Enterprise Linux Server 5.11 (Tikanga)',
++ 'version': '5.11',
++ 'pretty_version': '5.11 (Tikanga)',
++ 'best_version': '5.11',
++ 'codename': 'Tikanga',
++ 'major_version': '5',
++ 'minor_version': '11'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'redhat',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'version_id': '5.11',
++ 'codename': 'Tikanga'
++ }
++ self._test_release_file_info('redhat-release', desired_info)
++
++ def test_rhel6_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'pretty_name': 'Red Hat Enterprise Linux Server 6.5 (Santiago)',
++ 'version': '6.5',
++ 'pretty_version': '6.5 (Santiago)',
++ 'best_version': '6.5',
++ 'codename': 'Santiago',
++ 'major_version': '6',
++ 'minor_version': '5'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'redhat',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'version_id': '6.5',
++ 'codename': 'Santiago'
++ }
++ self._test_release_file_info('redhat-release', desired_info)
++
++ def test_rhel7_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'pretty_name': 'Red Hat Enterprise Linux Server 7.0 (Maipo)',
++ 'version': '7.0',
++ 'pretty_version': '7.0 (Maipo)',
++ 'best_version': '7.0',
++ 'like': 'fedora',
++ 'codename': 'Maipo',
++ 'major_version': '7',
++ 'minor_version': '0'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'redhat',
++ 'name': 'Red Hat Enterprise Linux Server',
++ 'version_id': '7.0',
++ 'codename': 'Maipo'
++ }
++ self._test_release_file_info('redhat-release', desired_info)
++
++ def test_slackware14_release(self):
++ desired_outcome = {
++ 'id': 'slackware',
++ 'name': 'Slackware',
++ 'pretty_name': 'Slackware 14.1',
++ 'version': '14.1',
++ 'pretty_version': '14.1',
++ 'best_version': '14.1',
++ 'major_version': '14',
++ 'minor_version': '1'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'slackware',
++ 'name': 'Slackware',
++ 'version_id': '14.1',
++ }
++ distro_info = self._test_release_file_info(
++ 'slackware-version', desired_info)
++ assert 'codename' not in distro_info
++
++ def test_sles12_release(self):
++ desired_outcome = {
++ 'id': 'sles',
++ 'name': 'SLES',
++ 'pretty_name': 'SUSE Linux Enterprise Server 12 SP1',
++ 'version': '12.1',
++ 'pretty_version': '12.1 (n/a)',
++ 'best_version': '12.1',
++ 'codename': 'n/a',
++ 'major_version': '12',
++ 'minor_version': '1'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'SuSE',
++ 'name': 'SUSE Linux Enterprise Server',
++ 'version_id': '12',
++ 'codename': 's390x'
++ }
++ self._test_release_file_info('SuSE-release', desired_info)
++
++ def test_ubuntu14_release(self):
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 14.04.3 LTS',
++ 'version': '14.04',
++ 'pretty_version': '14.04 (Trusty Tahr)',
++ 'best_version': '14.04.3',
++ 'like': 'debian',
++ 'codename': 'Trusty Tahr',
++ 'major_version': '14',
++ 'minor_version': '04'
++ }
++ self._test_outcome(desired_outcome)
++
++ # Test the info from the searched distro release file
++ # Does not have one; /etc/debian_version is not considered a distro
++ # release file:
++ self._test_non_existing_release_file()
++
++ def test_ubuntu16_release(self):
++ desired_outcome = {
++ 'id': 'ubuntu',
++ 'name': 'Ubuntu',
++ 'pretty_name': 'Ubuntu 16.04.1 LTS',
++ 'version': '16.04',
++ 'pretty_version': '16.04 (xenial)',
++ 'best_version': '16.04.1',
++ 'like': 'debian',
++ 'codename': 'xenial',
++ 'major_version': '16',
++ 'minor_version': '04'
++ }
++ self._test_outcome(desired_outcome)
++
++ # Test the info from the searched distro release file
++ # Does not have one; /etc/debian_version is not considered a distro
++ # release file:
++ self._test_non_existing_release_file()
++
++ def test_amazon2016_release(self):
++ desired_outcome = {
++ 'id': 'amzn',
++ 'name': 'Amazon Linux AMI',
++ 'pretty_name': 'Amazon Linux AMI 2016.03',
++ 'version': '2016.03',
++ 'pretty_version': '2016.03',
++ 'best_version': '2016.03',
++ 'like': 'rhel fedora',
++ 'major_version': '2016',
++ 'minor_version': '03'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_amazon2014_release(self):
++ # Amazon Linux 2014 only contains a system-release file.
++ # distro doesn't currently handle it.
++ desired_outcome = {}
++ self._test_outcome(desired_outcome)
++
++ def test_scientific6_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Scientific Linux',
++ 'pretty_name': 'Scientific Linux 6.4 (Carbon)',
++ 'version': '6.4',
++ 'pretty_version': '6.4 (Carbon)',
++ 'best_version': '6.4',
++ 'codename': 'Carbon',
++ 'major_version': '6',
++ 'minor_version': '4',
++
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'redhat',
++ 'name': 'Scientific Linux',
++ 'version_id': '6.4',
++ 'codename': 'Carbon'
++ }
++ self._test_release_file_info('redhat-release', desired_info)
++
++ def test_scientific7_release(self):
++ desired_outcome = {
++ 'id': 'rhel',
++ 'name': 'Scientific Linux',
++ 'pretty_name': 'Scientific Linux 7.2 (Nitrogen)',
++ 'version': '7.2',
++ 'pretty_version': '7.2 (Nitrogen)',
++ 'best_version': '7.2',
++ 'like': 'fedora',
++ 'codename': 'Nitrogen',
++ 'major_version': '7',
++ 'minor_version': '2',
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'redhat',
++ 'name': 'Scientific Linux',
++ 'version_id': '7.2',
++ 'codename': 'Nitrogen'
++ }
++ self._test_release_file_info('redhat-release', desired_info)
++
++ def test_gentoo_release(self):
++ desired_outcome = {
++ 'id': 'gentoo',
++ 'name': 'Gentoo',
++ 'pretty_name': 'Gentoo/Linux',
++ 'version': '2.2',
++ 'pretty_version': '2.2',
++ 'best_version': '2.2',
++ 'major_version': '2',
++ 'minor_version': '2',
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'gentoo',
++ 'name': 'Gentoo Base System',
++ 'version_id': '2.2',
++ }
++ self._test_release_file_info('gentoo-release', desired_info)
++
++ def test_openelec6_release(self):
++ desired_outcome = {
++ 'id': 'openelec',
++ 'name': 'OpenELEC',
++ 'pretty_name': 'OpenELEC (official) - Version: 6.0.3',
++ 'version': '6.0',
++ 'pretty_version': '6.0',
++ 'best_version': '6.0.3',
++ 'major_version': '6',
++ 'minor_version': '0',
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_mandriva2011_release(self):
++ desired_outcome = {
++ 'id': 'mandrivalinux',
++ 'name': 'MandrivaLinux',
++ 'pretty_name': 'Mandriva Linux 2011.0',
++ 'version': '2011.0',
++ 'pretty_version': '2011.0 (turtle)',
++ 'best_version': '2011.0',
++ 'major_version': '2011',
++ 'minor_version': '0',
++ 'codename': 'turtle'
++ }
++ self._test_outcome(desired_outcome)
++
++ desired_info = {
++ 'id': 'mandrake',
++ 'name': 'Mandriva Linux',
++ 'version_id': '2011.0',
++ }
++ self._test_release_file_info('mandrake-release', desired_info)
++
++ def test_cloudlinux5_release(self):
++ # Uses redhat-release only to get information.
++ # The id of 'rhel' can only be fixed with issue #109.
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Vladislav Volkov',
++ 'name': 'CloudLinux Server',
++ 'pretty_name': 'CloudLinux Server 5.11 (Vladislav Volkov)',
++ 'version': '5.11',
++ 'pretty_version': '5.11 (Vladislav Volkov)',
++ 'best_version': '5.11',
++ 'major_version': '5',
++ 'minor_version': '11'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_cloudlinux6_release(self):
++ # Same as above, only has redhat-release.
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Oleg Makarov',
++ 'name': 'CloudLinux Server',
++ 'pretty_name': 'CloudLinux Server 6.8 (Oleg Makarov)',
++ 'version': '6.8',
++ 'pretty_version': '6.8 (Oleg Makarov)',
++ 'best_version': '6.8',
++ 'major_version': '6',
++ 'minor_version': '8'
++ }
++ self._test_outcome(desired_outcome)
++
++ def test_cloudlinux7_release(self):
++ desired_outcome = {
++ 'id': 'cloudlinux',
++ 'codename': 'Yury Malyshev',
++ 'name': 'CloudLinux',
++ 'pretty_name': 'CloudLinux 7.3 (Yury Malyshev)',
++ 'like': 'rhel fedora centos',
++ 'version': '7.3',
++ 'pretty_version': '7.3 (Yury Malyshev)',
++ 'best_version': '7.3',
++ 'major_version': '7',
++ 'minor_version': '3'
++ }
++ self._test_outcome(desired_outcome)
++
++
++def _bad_os_listdir(path='.'):
++ """ This function is used by TestOverallWithEtcNotReadable to simulate
++ a folder that cannot be called with os.listdir() but files are still
++ readable. Forces distro to guess which *-release files are available. """
++ raise OSError()
++
++
++@pytest.mark.skipIf(not IS_LINUX, reason='Irrelevant on non-linx')
++class TestOverallWithEtcNotReadable(TestOverall):
++ def setup_method(self, test_method):
++ self._old_listdir = os.listdir
++ os.listdir = _bad_os_listdir
++ super(TestOverallWithEtcNotReadable, self).setup_method(test_method)
++
++ def teardown_method(self, test_method):
++ super(TestOverallWithEtcNotReadable, self).teardown_method(test_method)
++ if os.listdir is _bad_os_listdir:
++ os.listdir = self._old_listdir
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestGetAttr(DistroTestCase):
++ """Test the consistency between the results of
++ `{source}_release_attr()` and `{source}_release_info()` for all
++ distros in `DISTROS`.
++ """
++
++ def _test_attr(self, info_method, attr_method):
++ for dist in DISTROS:
++ self._setup_for_distro(os.path.join(DISTROS_DIR, dist))
++ _distro = distro.LinuxDistribution()
++ info = getattr(_distro, info_method)()
++ for key in info.keys():
++ try:
++ assert info[key] == getattr(_distro, attr_method)(key)
++ except AssertionError:
++ print("distro: {0}, key: {1}".format(dist, key))
++
++ def test_os_release_attr(self):
++ self._test_attr('os_release_info', 'os_release_attr')
++
++ def test_lsb_release_attr(self):
++ self._test_attr('lsb_release_info', 'lsb_release_attr')
++
++ def test_distro_release_attr(self):
++ self._test_attr('distro_release_info', 'distro_release_attr')
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestInfo(DistroTestCase):
++
++ def setup_method(self, test_method):
++ super(TestInfo, self).setup_method(test_method)
++ self.ubuntu14_os_release = os.path.join(
++ DISTROS_DIR, 'ubuntu14', 'etc', 'os-release')
++
++ def test_info(self):
++ _distro = distro.LinuxDistribution(
++ False, self.ubuntu14_os_release, 'non')
++
++ desired_info = {
++ 'id': 'ubuntu',
++ 'version': '14.04',
++ 'like': 'debian',
++ 'version_parts': {
++ 'major': '14',
++ 'minor': '04',
++ 'build_number': ''
++ },
++ 'codename': 'Trusty Tahr'
++ }
++
++ info = _distro.info()
++ assert info == desired_info
++
++ desired_info_diff = {
++ 'version': '14.04 (Trusty Tahr)'
++ }
++ desired_info.update(desired_info_diff)
++ info = _distro.info(pretty=True)
++ assert info == desired_info
++
++ desired_info_diff = {
++ 'version': '14.04.3',
++ 'version_parts': {
++ 'major': '14',
++ 'minor': '04',
++ 'build_number': '3'
++ }
++ }
++ desired_info.update(desired_info_diff)
++ info = _distro.info(best=True)
++ assert info == desired_info
++
++ desired_info_diff = {
++ 'version': '14.04.3 (Trusty Tahr)'
++ }
++ desired_info.update(desired_info_diff)
++ info = _distro.info(pretty=True, best=True)
++ assert info == desired_info
++
++ def test_none(self):
++
++ def _test_none(info):
++ assert info['id'] == ''
++ assert info['version'] == ''
++ assert info['like'] == ''
++ assert info['version_parts']['major'] == ''
++ assert info['version_parts']['minor'] == ''
++ assert info['version_parts']['build_number'] == ''
++ assert info['codename'] == ''
++
++ _distro = distro.LinuxDistribution(False, 'non', 'non')
++
++ info = _distro.info()
++ _test_none(info)
++
++ info = _distro.info(best=True)
++ _test_none(info)
++
++ info = _distro.info(pretty=True)
++ _test_none(info)
++
++ info = _distro.info(pretty=True, best=True)
++ _test_none(info)
++
++ def test_linux_distribution(self):
++ _distro = distro.LinuxDistribution(False, self.ubuntu14_os_release)
++ i = _distro.linux_distribution()
++ assert i == ('Ubuntu', '14.04', 'Trusty Tahr')
++
++ def test_linux_distribution_full_false(self):
++ _distro = distro.LinuxDistribution(False, self.ubuntu14_os_release)
++ i = _distro.linux_distribution(full_distribution_name=False)
++ assert i == ('ubuntu', '14.04', 'Trusty Tahr')
++
++ def test_all(self):
++ """Test info() by comparing its results with the results of specific
++ consolidated accessor functions.
++ """
++ def _test_all(info, best=False, pretty=False):
++ assert info['id'] == _distro.id()
++ assert info['version'] == _distro.version(pretty=pretty, best=best)
++ assert info['version_parts']['major'] == \
++ _distro.major_version(best=best)
++ assert info['version_parts']['minor'] == \
++ _distro.minor_version(best=best)
++ assert info['version_parts']['build_number'] == \
++ _distro.build_number(best=best)
++ assert info['like'] == _distro.like()
++ assert info['codename'] == _distro.codename()
++ assert len(info['version_parts']) == 3
++ assert len(info) == 5
++
++ for dist in DISTROS:
++ self._setup_for_distro(os.path.join(DISTROS_DIR, dist))
++
++ _distro = distro.LinuxDistribution()
++
++ info = _distro.info()
++ _test_all(info)
++
++ info = _distro.info(best=True)
++ _test_all(info, best=True)
++
++ info = _distro.info(pretty=True)
++ _test_all(info, pretty=True)
++
++ info = _distro.info(pretty=True, best=True)
++ _test_all(info, pretty=True, best=True)
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestOSReleaseParsing:
++ """Test the parsing of os-release files.
++ """
++
++ def setup_method(self, test_method):
++ self.distro = distro.LinuxDistribution(False, None, None)
++ self.distro.debug = True
++
++ def _get_props(self, input):
++ return self.distro._parse_os_release_content(StringIO(
++ input,
++ ))
++
++ def _test_zero_length_props(self, input):
++ props = self._get_props(input)
++ assert len(props) == 0
++
++ def _test_empty_value(self, input):
++ props = self._get_props(input)
++ assert props.get('key', None) == ''
++
++ def _test_parsed_value(self, input):
++ props = self._get_props(input)
++ assert props.get('key', None) == 'value'
++
++ def test_kv_01_empty_file(self):
++ self._test_zero_length_props('')
++
++ def test_kv_02_empty_line(self):
++ self._test_zero_length_props('\n')
++
++ def test_kv_03_empty_line_with_crlf(self):
++ self._test_zero_length_props('\r\n')
++
++ def test_kv_04_empty_line_with_just_cr(self):
++ self._test_zero_length_props('\r')
++
++ def test_kv_05_comment(self):
++ self._test_zero_length_props('# KEY=value\n')
++
++ def test_kv_06_empty_value(self):
++ self._test_empty_value('KEY=\n')
++
++ def test_kv_07_empty_value_single_quoted(self):
++ self._test_empty_value('KEY=\'\'\n')
++
++ def test_kv_08_empty_value_double_quoted(self):
++ self._test_empty_value('KEY=""\n')
++
++ def test_kv_09_word(self):
++ self._test_parsed_value('KEY=value\n')
++
++ def test_kv_10_word_no_newline(self):
++ self._test_parsed_value('KEY=value')
++
++ def test_kv_11_word_with_crlf(self):
++ self._test_parsed_value('KEY=value\r\n')
++
++ def test_kv_12_word_with_just_cr(self):
++ self._test_parsed_value('KEY=value\r')
++
++ def test_kv_13_word_with_multi_blanks(self):
++ self._test_empty_value('KEY= cmd \n')
++ # Note: Without quotes, this assigns the empty string, and 'cmd' is
++ # a separate token that is being ignored (it would be a command
++ # in the shell).
++
++ def test_kv_14_unquoted_words(self):
++ self._test_parsed_value('KEY=value cmd\n')
++
++ def test_kv_15_double_quoted_words(self):
++ props = self._get_props('KEY="a simple value" cmd\n')
++ assert props.get('key', None) == 'a simple value'
++
++ def test_kv_16_double_quoted_words_with_multi_blanks(self):
++ props = self._get_props('KEY=" a simple value "\n')
++ assert props.get('key', None) == ' a simple value '
++
++ def test_kv_17_double_quoted_word_with_single_quote(self):
++ props = self._get_props('KEY="it\'s value"\n')
++ assert props.get('key', None) == 'it\'s value'
++
++ def test_kv_18_double_quoted_word_with_double_quote(self):
++ props = self._get_props('KEY="a \\"bold\\" move"\n')
++ assert props.get('key', None) == 'a "bold" move'
++
++ def test_kv_19_single_quoted_words(self):
++ props = self._get_props('KEY=\'a simple value\'\n')
++ assert props.get('key', None) == 'a simple value'
++
++ def test_kv_20_single_quoted_words_with_multi_blanks(self):
++ props = self._get_props('KEY=\' a simple value \'\n')
++ assert props.get('key', None) == ' a simple value '
++
++ def test_kv_21_single_quoted_word_with_double_quote(self):
++ props = self._get_props('KEY=\'a "bold" move\'\n')
++ assert props.get('key', None) == 'a "bold" move'
++
++ def test_kv_22_quoted_unicode_wordchar(self):
++ # "wordchar" means it is in the shlex.wordchars variable.
++ props = self._get_props(u'KEY="wordchar: \u00CA (E accent grave)"\n')
++ assert props.get('key', None) == u'wordchar: \u00CA (E accent grave)'
++
++ def test_kv_23_quoted_unicode_non_wordchar(self):
++ # "non-wordchar" means it is not in the shlex.wordchars variable.
++ props = self._get_props(
++ u'KEY="non-wordchar: \u00A1 (inverted exclamation mark)"\n')
++ assert (props.get('key', None) ==
++ u'non-wordchar: \u00A1 (inverted exclamation mark)')
++
++ def test_kv_24_double_quoted_entire_single_quoted_word(self):
++ props = self._get_props('KEY="\'value\'"\n')
++ assert props.get('key', None) == "'value'"
++
++ def test_kv_25_single_quoted_entire_double_quoted_word(self):
++ props = self._get_props('KEY=\'"value"\'\n')
++ assert props.get('key', None) == '"value"'
++
++ def test_kv_26_double_quoted_multiline(self):
++ props = self.distro._parse_os_release_content(StringIO(
++ 'KEY="a multi\n'
++ 'line value"\n'
++ ))
++ assert props.get('key', None) == 'a multi\nline value'
++ # TODO: Find out why the result is not 'a multi line value'
++
++ def test_kv_27_double_quoted_multiline_2(self):
++ props = self._get_props('KEY=\' a simple value \'\n')
++ props = self.distro._parse_os_release_content(StringIO(
++ 'KEY="a multi\n'
++ 'line=value"\n'
++ ))
++ assert props.get('key', None) == 'a multi\nline=value'
++ # TODO: Find out why the result is not 'a multi line=value'
++
++ def test_kv_28_double_quoted_word_with_equal(self):
++ props = self._get_props('KEY="var=value"\n')
++ assert props.get('key', None) == 'var=value'
++
++ def test_kv_29_single_quoted_word_with_equal(self):
++ props = self._get_props('KEY=\'var=value\'\n')
++ assert props.get('key', None) == 'var=value'
++
++ def test_kx_01(self):
++ props = self.distro._parse_os_release_content(StringIO(
++ 'KEY1=value1\n'
++ 'KEY2="value 2"\n'
++ ))
++ assert props.get('key1', None) == 'value1'
++ assert props.get('key2', None) == 'value 2'
++
++ def test_kx_02(self):
++ props = self.distro._parse_os_release_content(StringIO(
++ '# KEY1=value1\n'
++ 'KEY2="value 2"\n'
++ ))
++ assert props.get('key1', None) is None
++ assert props.get('key2', None) == 'value 2'
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestGlobal:
++ """Test the global module-level functions, and default values of their
++ arguments.
++ """
++
++ def setup_method(self, test_method):
++ pass
++
++ def test_global(self):
++ # Because the module-level functions use the module-global
++ # LinuxDistribution instance, it would influence the tested
++ # code too much if we mocked that in order to use the distro
++ # specific release files. Instead, we let the functions use
++ # the release files of the distro this test runs on, and
++ # compare the result of the global functions with the result
++ # of the methods on the global LinuxDistribution object.
++
++ def _test_consistency(function, kwargs=None):
++ kwargs = kwargs or {}
++ method_result = getattr(MODULE_DISTRO, function)(**kwargs)
++ function_result = getattr(distro, function)(**kwargs)
++ assert method_result == function_result
++
++ kwargs = {'full_distribution_name': True}
++ _test_consistency('linux_distribution', kwargs)
++ kwargs = {'full_distribution_name': False}
++ _test_consistency('linux_distribution', kwargs)
++
++ kwargs = {'pretty': False}
++ _test_consistency('name', kwargs)
++ _test_consistency('version', kwargs)
++ _test_consistency('info', kwargs)
++
++ kwargs = {'pretty': True}
++ _test_consistency('name', kwargs)
++ _test_consistency('version', kwargs)
++ _test_consistency('info', kwargs)
++
++ kwargs = {'best': False}
++ _test_consistency('version', kwargs)
++ _test_consistency('version_parts', kwargs)
++ _test_consistency('major_version', kwargs)
++ _test_consistency('minor_version', kwargs)
++ _test_consistency('build_number', kwargs)
++ _test_consistency('info', kwargs)
++
++ kwargs = {'best': True}
++ _test_consistency('version', kwargs)
++ _test_consistency('version_parts', kwargs)
++ _test_consistency('major_version', kwargs)
++ _test_consistency('minor_version', kwargs)
++ _test_consistency('build_number', kwargs)
++ _test_consistency('info', kwargs)
++
++ _test_consistency('id')
++ _test_consistency('like')
++ _test_consistency('codename')
++ _test_consistency('info')
++
++ _test_consistency('os_release_info')
++ _test_consistency('lsb_release_info')
++ _test_consistency('distro_release_info')
++ _test_consistency('uname_info')
++
++ os_release_keys = [
++ 'name',
++ 'version',
++ 'id',
++ 'id_like',
++ 'pretty_name',
++ 'version_id',
++ 'codename',
++ ]
++ for key in os_release_keys:
++ _test_consistency('os_release_attr', {'attribute': key})
++
++ lsb_release_keys = [
++ 'distributor_id',
++ 'description',
++ 'release',
++ 'codename',
++ ]
++ for key in lsb_release_keys:
++ _test_consistency('lsb_release_attr', {'attribute': key})
++
++ distro_release_keys = [
++ 'id',
++ 'name',
++ 'version_id',
++ 'codename',
++ ]
++ for key in distro_release_keys:
++ _test_consistency('distro_release_attr', {'attribute': key})
++
++ uname_keys = [
++ 'id',
++ 'name',
++ 'release'
++ ]
++ for key in uname_keys:
++ _test_consistency('uname_attr', {'attribute': key})
++
++
++@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux')
++class TestRepr:
++ """Test the __repr__() method.
++ """
++
++ def test_repr(self):
++ # We test that the class name and the names of all instance attributes
++ # show up in the repr() string.
++ repr_str = repr(distro._distro)
++ assert "LinuxDistribution" in repr_str
++ for attr in MODULE_DISTRO.__dict__.keys():
++ assert attr + '=' in repr_str
+diff --git a/third_party/python/enum34/enum/doc/enum.pdf b/third_party/python/enum34/enum/doc/enum.pdf
+--- a/third_party/python/enum34/enum/doc/enum.pdf
++++ b/third_party/python/enum34/enum/doc/enum.pdf
+@@ -1,10 +1,10 @@
+ %PDF-1.4
+-%東京 ReportLab Generated PDF document http://www.reportlab.com
++%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
+ 1 0 obj
+ << /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 8 0 R /F6 15 0 R >>
+ endobj
+ 2 0 obj
+ << /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
+ endobj
+ 3 0 obj
+ << /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font >>
+diff --git a/third_party/python/psutil/psutil/_pslinux.py b/third_party/python/psutil/psutil/_pslinux.py
+--- a/third_party/python/psutil/psutil/_pslinux.py
++++ b/third_party/python/psutil/psutil/_pslinux.py
+@@ -1046,30 +1046,28 @@ def disk_io_counters():
+ # On Linux 2.4 each line has always 15 fields, e.g.:
+ # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8"
+ # On Linux 2.6+ each line *usually* has 14 fields, and the disk
+ # name is in another position, like this:
+ # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8"
+ # ...unless (Linux 2.6) the line refers to a partition instead
+ # of a disk, in which case the line has less fields (7):
+ # "3 1 hda1 8 8 8 8"
+- # 4.18+ has 4 fields added:
+- # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0"
+ # See:
+ # https://www.kernel.org/doc/Documentation/iostats.txt
+ # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
+ fields = line.split()
+ fields_len = len(fields)
+ if fields_len == 15:
+ # Linux 2.4
+ name = fields[3]
+ reads = int(fields[2])
+ (reads_merged, rbytes, rtime, writes, writes_merged,
+ wbytes, wtime, _, busy_time, _) = map(int, fields[4:14])
+- elif fields_len == 14 or fields_len == 18:
++ elif fields_len == 14:
+ # Linux 2.6+, line referring to a disk
+ name = fields[2]
+ (reads, reads_merged, rbytes, rtime, writes, writes_merged,
+ wbytes, wtime, _, busy_time, _) = map(int, fields[3:14])
+ elif fields_len == 7:
+ # Linux 2.6+, line referring to a partition
+ name = fields[2]
+ reads, rbytes, writes, wbytes = map(int, fields[3:])
+diff --git a/third_party/python/requirements.in b/third_party/python/requirements.in
+--- a/third_party/python/requirements.in
++++ b/third_party/python/requirements.in
+@@ -1,11 +1,12 @@
+ attrs==18.1.0
+ biplist==1.0.3
+ blessings==1.7
++distro==1.4.0
+ jsmin==2.1.0
+ json-e==2.7.0
+ mozilla-version==0.3.0
+ pathlib2==2.3.2
+ pip-tools==3.0.0
+ pipenv==2018.5.18
+ psutil==5.4.3
+ pytest==3.6.2
+diff --git a/third_party/python/requirements.txt b/third_party/python/requirements.txt
+--- a/third_party/python/requirements.txt
++++ b/third_party/python/requirements.txt
+@@ -14,16 +14,19 @@ blessings==1.7 \
+ certifi==2018.4.16 \
+ --hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
+ --hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0 \
+ # via pipenv
+ click==7.0 \
+ --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \
+ --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 \
+ # via pip-tools
++distro==1.4.0 \
++ --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \
++ --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4
+ enum34==1.1.6 \
+ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \
+ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \
+ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \
+ --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 \
+ # via mozilla-version
+ funcsigs==1.0.2 \
+ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \
+