#!/usr/bin/python
# vim: set fileencoding=utf-8 ft=python sw=4 ts=4 et :
#
"""UCS Testrunner - run UCS test in sane environment."""
#
import sys
import logging
import operator
from time import time
from optparse import OptionParser, OptionGroup

import univention.testing.format
from univention.testing.codes import TestCodes
from univention.testing.utils import setup_environment, setup_debug, \
		LOG_BASE, get_sections, get_tests
from univention.testing.data import TestEnvironment, TestCase, TestResult


def parse_options(sections):
	"""Parse command line options."""
	usage = "Usage: %prog [options]"
	parser = OptionParser(usage=usage)

	selection_group = OptionGroup(parser, 'Test selection')
	selection_group.add_option("-s", "--section",
			dest="sections", action="append", choices=sections,
			help="Run tests only from this section", metavar="SECTION")
	selection_group.add_option("-p", "--prohibit",
			dest="tags_prohibited", action="append", default=[],
			help="Skipt tests with this tag", metavar="TAG")
	selection_group.add_option("-r", "--require",
			dest="tags_required", action="append", default=[],
			help="Only run tests with this tag", metavar="TAG")
	selection_group.add_option("-g", "--ignore",
			dest="tags_ignored", action="append", default=[],
			help="Neither require nor prohibt this tag", metavar="TAG")
	selection_group.add_option("-E", "--exposure",
			dest="exposure", action="store",
			choices=('safe', 'careful', 'dangerous'),
			help="Run more dangerous tests")
	selection_group.add_option("-H", "--hold",
			dest="hold", action="store_true", default=False,
			help="Stop execution after the first failed test")
	parser.add_option_group(selection_group)

	output_group = OptionGroup(parser, 'Output options')
	output_group.add_option("-n", "--dry-run",
			dest="dry", action="store_true",
			help="Only show which tests would run")
	output_group.add_option("-f", "--filtered",
			dest="filter", action="store_true",
			help="Hide tests with unmatched pre-conditions")
	output_group.add_option("-F", "--format",
			dest="format", action="store",
			choices=univention.testing.format.FORMATS, default='text',
			help="Select output format [%default]")
	output_group.add_option("-v", "--verbose",
			dest="verbose", action="count",
			help="Increase verbosity")
	output_group.add_option("-i", "--interactive",
			dest="interactive", action="store_true", default=False,
			help="Run test connected to terminal")
	output_group.add_option("-c", "--count",
			dest="count", action="store_true", default=False,
			help="Prefix tests by count")
	output_group.add_option("-l", "--logfile",
			dest="logfile", action="store", default=LOG_BASE % (time(),),
			help="Path to log file [%default]")

	parser.add_option_group(output_group)

	return parser.parse_args()


class TestSet(object):
	"""Container for tests."""

	def __init__(self, tests):
		self.tests = tests
		self.max_count = reduce(operator.add, [len(_) for _ in tests.values()])
		self.test_environment = None
		self.format = None
		self.prefix = ''

	def set_environment(self, test_environment):
		"""Set environment for running tests."""
		self.test_environment = test_environment

	def set_format(self, format):
		"""Select output format."""
		formatter = getattr(univention.testing.format, 'format_%s' % (format,))
		self.format = formatter()

	def set_prefix(self, prefix):
		"""Enable or disable test numbering."""
		if prefix:
			count_width = len('%d' % (self.max_count,))
			self.prefix = '%%0%dd/%%0%dd ' % (count_width, count_width)
		else:
			self.prefix = ''

	def run_tests(self, filter_condition=False, dry_run=False, stop_on_failure=False):
		"""Run selected tests."""
		self.format.begin_run(self.test_environment, self.max_count)
		try:
			count = 0
			for section, cases in sorted(self.tests.items(), key=operator.itemgetter(1)):
				self.format.begin_section(section)
				try:
					for fname in cases:
						count += 1
						test_case = TestCase().load(fname)
						test_result = TestResult(test_case,
								self.test_environment)
						check = test_result.check()
						if filter_condition and not check:
							continue
						if self.prefix:
							self.format.begin_test(test_case,
									self.prefix % (count, self.max_count))
						else:
							self.format.begin_test(test_case)
						try:
							if not dry_run:
								test_result.run()
								if stop_on_failure and \
									test_result.result == TestCodes.RESULT_FAIL:
									# stop execution, Bug #35988
									return
						finally:
							self.format.end_test(test_result)
				finally:
					self.format.end_section()
		finally:
			self.format.end_run()


def main():
	"""Run UCS test suite."""
	all_sections = get_sections()

	(options, args) = parse_options(all_sections.keys())
	if args:
		logger = logging.getLogger('test')
		logger.error('Unused arguments: %r', args)
		sys.exit(2)

	setup_environment()
	setup_debug(options.verbose)

	if options.sections:
		selected_sections = options.sections
	else:
		selected_sections = all_sections.keys()
	tests = get_tests(selected_sections)

	test_set = TestSet(tests)

	if options.dry:
		test_environment = TestEnvironment(interactive=options.interactive)
	else:
		test_environment = TestEnvironment(interactive=options.interactive,
				logfile=options.logfile)
	test_environment.tag(require=options.tags_required,
			ignore=options.tags_ignored,
			prohibit=options.tags_prohibited)
	if options.exposure:
		test_environment.set_exposure(options.exposure)
	test_set.set_environment(test_environment)
	test_set.set_prefix(options.count)
	test_set.set_format(options.format)
	test_set.run_tests(options.filter, options.dry, options.hold)


if __name__ == '__main__':
	try:
		main()
	except KeyboardInterrupt:
		sys.exit(1)
