#!/usr/bin/env python
# Copyright 2010-2017 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
#
# Note: We don't want to import portage modules directly because we do things
# like run the testsuite through multiple versions of python.

"""Helper script to run portage unittests against different python versions.

Note: Any additional arguments will be passed down directly to the underlying
unittest runner.  This lets you select specific tests to execute.
"""

from __future__ import print_function

import argparse
import os
import shutil
import subprocess
import sys
import tempfile


# These are the versions we fully support and require to pass tests.
PYTHON_SUPPORTED_VERSIONS = [
	'2.7',
	'3.6',
	'3.7',
	'3.8'
]
# The rest are just "nice to have".
PYTHON_NICE_VERSIONS = [
	'pypy3',
	'3.9'
]

EPREFIX = os.environ.get('PORTAGE_OVERRIDE_EPREFIX', '/')


class Colors(object):
	"""Simple object holding color constants."""

	_COLORS_YES = ('y', 'yes', 'true')
	_COLORS_NO = ('n', 'no', 'false')

	WARN = GOOD = BAD = NORMAL = ''

	def __init__(self, colorize=None):
		if colorize is None:
			nocolors = os.environ.get('NOCOLOR', 'false')
			# Ugh, look away, for here we invert the world!
			if nocolors in self._COLORS_YES:
				colorize = False
			elif nocolors in self._COLORS_NO:
				colorize = True
			else:
				raise ValueError('$NOCOLORS is invalid: %s' % nocolors)
		else:
			if colorize in self._COLORS_YES:
				colorize = True
			elif colorize in self._COLORS_NO:
				colorize = False
			else:
				raise ValueError('--colors is invalid: %s' % colorize)

		if colorize:
			self.WARN = '\033[1;33m'
			self.GOOD = '\033[1;32m'
			self.BAD = '\033[1;31m'
			self.NORMAL = '\033[0m'


def get_python_executable(ver):
	"""Find the right python executable for |ver|"""
	if ver in ('pypy', 'pypy3'):
		prog = ver
	else:
		prog = 'python' + ver
	return os.path.join(EPREFIX, 'usr', 'bin', prog)


def get_parser():
	"""Return a argument parser for this module"""
	epilog = """Examples:
List all the available unittests.
$ %(prog)s --list

Run against specific versions of python.
$ %(prog)s --python-versions '2.7 3.3'

Run just one unittest.
$ %(prog)s lib/portage/tests/xpak/test_decodeint.py
"""
	parser = argparse.ArgumentParser(
		description=__doc__,
		formatter_class=argparse.RawDescriptionHelpFormatter,
		epilog=epilog)
	parser.add_argument('--keep-temp', default=False, action='store_true',
		help='Do not delete the temporary directory when exiting')
	parser.add_argument('--color', type=str, default=None,
		help='Whether to use colorized output (default is auto)')
	parser.add_argument('--python-versions', action='append',
		help='Versions of python to test (default is test available)')
	return parser


def main(argv):
	parser = get_parser()
	opts, args = parser.parse_known_args(argv)
	colors = Colors(colorize=opts.color)

	# Figure out all the versions we want to test.
	if opts.python_versions is None:
		ignore_missing = True
		pyversions = PYTHON_SUPPORTED_VERSIONS + PYTHON_NICE_VERSIONS
	else:
		ignore_missing = False
		pyversions = []
		for ver in opts.python_versions:
			if ver == 'supported':
				pyversions.extend(PYTHON_SUPPORTED_VERSIONS)
			else:
				pyversions.extend(ver.split())

	here = os.path.dirname(__file__)
	run_path = os.path.join(here, 'lib/repoman/tests/runTests.py')
	tempdir = None
	try:
		# Set up a single tempdir for all the tests to use.
		# This way we know the tests won't leak things on us.
		tempdir = tempfile.mkdtemp(prefix='repoman.runtests.')
		os.environ['TMPDIR'] = tempdir

		# Actually test those versions now.
		statuses = []
		for ver in pyversions:
			prog = get_python_executable(ver)
			cmd = [prog, '-b', '-Wd', run_path] + args
			if os.access(prog, os.X_OK):
				print('%sTesting with Python %s...%s' %
					(colors.GOOD, ver, colors.NORMAL))
				statuses.append((ver, subprocess.call(cmd)))
			elif not ignore_missing:
				print('%sCould not find requested Python %s%s' %
					(colors.BAD, ver, colors.NORMAL))
				statuses.append((ver, 1))
			else:
				print('%sSkip Python %s...%s' %
					(colors.WARN, ver, colors.NORMAL))
			print()
	finally:
		if tempdir is not None:
			if opts.keep_temp:
				print('Temporary directory left behind:\n%s' % tempdir)
			else:
				# Nuke our tempdir and anything that might be under it.
				shutil.rmtree(tempdir, True)

	# Then summarize it all.
	print('\nSummary:\n')
	width = 10
	header = '| %-*s | %s' % (width, 'Version', 'Status')
	print('%s\n|%s' % (header, '-' * (len(header) - 1)))
	exit_status = 0
	for ver, status in statuses:
		exit_status += status
		if status:
			color = colors.BAD
			msg = 'FAIL'
		else:
			color = colors.GOOD
			msg = 'PASS'
		print('| %s%-*s%s | %s%s%s' %
			(color, width, ver, colors.NORMAL, color, msg, colors.NORMAL))
	exit(exit_status)


if __name__ == '__main__':
	try:
		main(sys.argv[1:])
	except KeyboardInterrupt:
		print('interrupted ...', file=sys.stderr)
		exit(1)
